You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@royale.apache.org by jo...@apache.org on 2019/09/18 22:02:45 UTC

[royale-asjs] branch develop updated: RoyaleUnit: Started implementing async tests

This is an automated email from the ASF dual-hosted git repository.

joshtynjala pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/royale-asjs.git


The following commit(s) were added to refs/heads/develop by this push:
     new 542446f  RoyaleUnit: Started implementing async tests
542446f is described below

commit 542446fadb47e9b8374ac109ea191f0c920592de
Author: Josh Tynjala <jo...@apache.org>
AuthorDate: Wed Sep 18 15:02:36 2019 -0700

    RoyaleUnit: Started implementing async tests
    
    Tests must be defined with [Test(async)] metadata.
    Can use Async.delayCall() to call a function with asserts later. Other helper functions still need to be implemented.
---
 .../src/main/royale/RoyaleUnitClasses.as           |   3 +
 .../royale/org/apache/royale/test/async/Async.as   |  35 ++++++
 .../org/apache/royale/test/async/AsyncLocator.as   | 107 ++++++++++++++++
 .../org/apache/royale/test/async/IAsyncHandler.as  |  33 +++++
 .../apache/royale/test/runners/MetadataRunner.as   | 134 +++++++++++++++++----
 .../RoyaleUnit/src/test/royale/tests/AsyncTests.as |  85 ++++++++++++-
 6 files changed, 370 insertions(+), 27 deletions(-)

diff --git a/frameworks/projects/RoyaleUnit/src/main/royale/RoyaleUnitClasses.as b/frameworks/projects/RoyaleUnit/src/main/royale/RoyaleUnitClasses.as
index 0088dcc..b406d94 100644
--- a/frameworks/projects/RoyaleUnit/src/main/royale/RoyaleUnitClasses.as
+++ b/frameworks/projects/RoyaleUnit/src/main/royale/RoyaleUnitClasses.as
@@ -38,6 +38,9 @@ internal class RoyaleUnitClasses
 	import org.apache.royale.test.asserts.assertStrictlyEquals;assertStrictlyEquals;
 	import org.apache.royale.test.asserts.assertTrue;assertTrue;
 	import org.apache.royale.test.asserts.fail;fail;
+	import org.apache.royale.test.async.Async;Async;
+	import org.apache.royale.test.async.AsyncLocator;AsyncLocator;
+	import org.apache.royale.test.async.IAsyncHandler;IAsyncHandler;
 	import org.apache.royale.test.listeners.CIListener;CIListener;
 	import org.apache.royale.test.listeners.FailureListener;FailureListener;
 	import org.apache.royale.test.listeners.TraceListener;TraceListener
diff --git a/frameworks/projects/RoyaleUnit/src/main/royale/org/apache/royale/test/async/Async.as b/frameworks/projects/RoyaleUnit/src/main/royale/org/apache/royale/test/async/Async.as
new file mode 100644
index 0000000..206d330
--- /dev/null
+++ b/frameworks/projects/RoyaleUnit/src/main/royale/org/apache/royale/test/async/Async.as
@@ -0,0 +1,35 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  Licensed to the Apache Software Foundation (ASF) under one or more
+//  contributor license agreements.  See the NOTICE file distributed with
+//  this work for additional information regarding copyright ownership.
+//  The ASF licenses this file to You under the Apache License, Version 2.0
+//  (the "License"); you may not use this file except in compliance with
+//  the License.  You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Unless required by applicable law or agreed to in writing, software
+//  distributed under the License is distributed on an "AS IS" BASIS,
+//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//  See the License for the specific language governing permissions and
+//  limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+package org.apache.royale.test.async
+{
+	/**
+	 * Helper functions for tests marked with <code>[Test(async)]</code> metadata.
+	 */
+	public class Async
+	{
+		public static function delayCall(testCase:Object, delayedFunction:Function, delayMS:int):void
+		{
+			var handler:IAsyncHandler = AsyncLocator.getAsyncHandlerForTest(testCase);
+			handler.asyncHandler(function():void
+			{
+				delayedFunction();
+			}, delayMS);
+		}
+	}
+}
\ No newline at end of file
diff --git a/frameworks/projects/RoyaleUnit/src/main/royale/org/apache/royale/test/async/AsyncLocator.as b/frameworks/projects/RoyaleUnit/src/main/royale/org/apache/royale/test/async/AsyncLocator.as
new file mode 100644
index 0000000..aa0721f
--- /dev/null
+++ b/frameworks/projects/RoyaleUnit/src/main/royale/org/apache/royale/test/async/AsyncLocator.as
@@ -0,0 +1,107 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  Licensed to the Apache Software Foundation (ASF) under one or more
+//  contributor license agreements.  See the NOTICE file distributed with
+//  this work for additional information regarding copyright ownership.
+//  The ASF licenses this file to You under the Apache License, Version 2.0
+//  (the "License"); you may not use this file except in compliance with
+//  the License.  You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Unless required by applicable law or agreed to in writing, software
+//  distributed under the License is distributed on an "AS IS" BASIS,
+//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//  See the License for the specific language governing permissions and
+//  limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+package org.apache.royale.test.async
+{
+	import org.apache.royale.test.AssertionError;
+	
+	COMPILE::SWF
+	{
+		import flash.utils.Dictionary;
+	}
+
+	/**
+	 * Used for tests with <code>[Test(async)]</code> metadata.
+	 */
+	COMPILE::SWF
+	public class AsyncLocator
+	{
+		private static var handlers:Dictionary;
+
+		public static function setAsyncHandlerForTest(test:Object, handler:IAsyncHandler):void
+		{
+			if(!handlers)
+			{
+				handlers = new Dictionary()
+			}
+			handlers[test] = handler;
+		}
+
+		public static function getAsyncHandlerForTest(test:Object):IAsyncHandler
+		{
+			if(!handlers || !hasAsyncHandlerForTest(test))
+			{
+				throw new AssertionError(MISSING_ASYNC_ERROR_MESSAGE);
+			}
+			return handlers[test];
+		}
+
+		public static function clearAsyncHandlerForTest(test:Object):void
+		{
+			if(!handlers || !hasAsyncHandlerForTest(test))
+			{
+				return;
+			}
+			delete handlers[test];
+		}
+
+		public static function hasAsyncHandlerForTest(test:Object):Boolean
+		{
+			return test in handlers;
+		}
+	}
+	
+	/**
+	 * Used for tests with <code>[Test(async)]</code> metadata.
+	 */
+	COMPILE::JS
+	public class AsyncLocator
+	{
+		private static var handlers:Map = new Map();
+
+		public static function setAsyncHandlerForTest(test:Object, handler:IAsyncHandler):void
+		{
+			handlers.set(test, handler);
+		}
+
+		public static function getAsyncHandlerForTest(test:Object):IAsyncHandler
+		{
+			if(!handlers || !hasAsyncHandlerForTest(test))
+			{
+				throw new AssertionError(MISSING_ASYNC_ERROR_MESSAGE);
+			}
+			return IAsyncHandler(handlers.get(test));
+		}
+
+		public static function clearAsyncHandlerForTest(test:Object):void
+		{
+			if(!handlers || !hasAsyncHandlerForTest(test))
+			{
+				return;
+			}
+			handlers.delete(test);
+		}
+
+		public static function hasAsyncHandlerForTest(test:Object):Boolean
+		{
+			return handlers.has(test);
+		}
+	}
+}
+
+const MISSING_ASYNC_ERROR_MESSAGE:String = "Cannot add asynchronous functionality to methods defined by Test, Before, or After that are not marked async"
\ No newline at end of file
diff --git a/frameworks/projects/RoyaleUnit/src/main/royale/org/apache/royale/test/async/IAsyncHandler.as b/frameworks/projects/RoyaleUnit/src/main/royale/org/apache/royale/test/async/IAsyncHandler.as
new file mode 100644
index 0000000..86652b1
--- /dev/null
+++ b/frameworks/projects/RoyaleUnit/src/main/royale/org/apache/royale/test/async/IAsyncHandler.as
@@ -0,0 +1,33 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  Licensed to the Apache Software Foundation (ASF) under one or more
+//  contributor license agreements.  See the NOTICE file distributed with
+//  this work for additional information regarding copyright ownership.
+//  The ASF licenses this file to You under the Apache License, Version 2.0
+//  (the "License"); you may not use this file except in compliance with
+//  the License.  You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Unless required by applicable law or agreed to in writing, software
+//  distributed under the License is distributed on an "AS IS" BASIS,
+//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//  See the License for the specific language governing permissions and
+//  limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+package org.apache.royale.test.async
+{
+	/**
+	 * Used for tests with <code>[Test(async)]</code> metadata.
+	 */
+	public interface IAsyncHandler
+	{
+		/**
+		 * Indicates if the body of the test function is currently executing.
+		 */
+		function get bodyExecuting():Boolean;
+
+		function asyncHandler(eventListener:Function, delay:int):void;
+	}
+}
\ No newline at end of file
diff --git a/frameworks/projects/RoyaleUnit/src/main/royale/org/apache/royale/test/runners/MetadataRunner.as b/frameworks/projects/RoyaleUnit/src/main/royale/org/apache/royale/test/runners/MetadataRunner.as
index 8197465..8bb3d19 100644
--- a/frameworks/projects/RoyaleUnit/src/main/royale/org/apache/royale/test/runners/MetadataRunner.as
+++ b/frameworks/projects/RoyaleUnit/src/main/royale/org/apache/royale/test/runners/MetadataRunner.as
@@ -25,6 +25,8 @@ package org.apache.royale.test.runners
 	import org.apache.royale.reflection.describeType;
 	import org.apache.royale.reflection.getQualifiedClassName;
 	import org.apache.royale.test.AssertionError;
+	import org.apache.royale.test.async.AsyncLocator;
+	import org.apache.royale.test.async.IAsyncHandler;
 	import org.apache.royale.test.runners.notification.Failure;
 	import org.apache.royale.test.runners.notification.IRunListener;
 	import org.apache.royale.test.runners.notification.IRunNotifier;
@@ -149,6 +151,11 @@ package org.apache.royale.test.runners
 		public function pleaseStop():void
 		{
 			_stopRequested = true;
+			if(_timer)
+			{
+				_timer.stop();
+				cleanupAsync();
+			}
 		}
 
 		/**
@@ -234,12 +241,20 @@ package org.apache.royale.test.runners
 				{
 					_before.apply(_target);
 				}
+				var asyncHandler:AsyncHandler = null;
+				if(test.asyncTimeout > 0)
+				{
+					asyncHandler = new AsyncHandler();
+					AsyncLocator.setAsyncHandlerForTest(_target, asyncHandler);
+					asyncHandler.bodyExecuting = true;
+				}
 				test.reference.apply(_target);
-				if(test.async)
+				if(asyncHandler)
 				{
-					var timeout:int = getTimeout(test);
-					_timer = new Timer(timeout, 1);
+					asyncHandler.bodyExecuting = false;
+					_timer = new Timer(test.asyncTimeout, 1);
 					_timer.addEventListener(Timer.TIMER, timer_timerHandler);
+					_timer.start();
 					return false;
 				}
 			}
@@ -333,6 +348,7 @@ package org.apache.royale.test.runners
 				var testFunction:Function = null;
 				var ignore:Boolean = false;
 				var async:Boolean = false;
+				var asyncTimeout:int = 0;
 
 				var testMetadata:Array = method.retrieveMetaDataByName(TestMetadata.TEST);
 				if(testMetadata.length > 0)
@@ -349,7 +365,18 @@ package org.apache.royale.test.runners
 					qualifiedName += lastPart;
 					testName = qualifiedName + "." + method.name;
 					testFunction = _target[method.name];
-					async = testTag.getArgsByKey("async").length > 0;
+					if(testTag.getArgsByKey("async").length > 0)
+					{
+						var timeoutArgs:Array = testTag.getArgsByKey("timeout");
+						if(timeoutArgs.length > 0)
+						{
+							asyncTimeout = parseFloat(timeoutArgs[0].value);
+						}
+						else
+						{
+							asyncTimeout = DEFAULT_ASYNC_TIMEOUT;
+						}
+					}
 				}
 				var ignoreMetadata:Array = method.retrieveMetaDataByName(TestMetadata.IGNORE);
 				if(ignoreMetadata.length > 0)
@@ -358,7 +385,7 @@ package org.apache.royale.test.runners
 				}
 				if(testName !== null)
 				{
-					_collectedTests.push(new TestInfo(testName, testFunction, ignore, async));
+					_collectedTests.push(new TestInfo(testName, testFunction, ignore, asyncTimeout));
 				}
 			}
 		}
@@ -366,18 +393,12 @@ package org.apache.royale.test.runners
 		/**
 		 * @private
 		 */
-		protected function getTimeout(test:TestInfo):int
-		{
-			return DEFAULT_ASYNC_TIMEOUT;
-		}
-
-		/**
-		 * @private
-		 */
-		protected function cleanupTimer():void
+		protected function cleanupAsync():void
 		{
 			_timer.removeEventListener(Timer.TIMER, timer_timerHandler);
 			_timer = null;
+
+			AsyncLocator.clearAsyncHandlerForTest(_target);
 		}
 
 		/**
@@ -385,33 +406,98 @@ package org.apache.royale.test.runners
 		 */
 		protected function timer_timerHandler(event:Event):void
 		{
-			cleanupTimer();
+			var asyncHandler:AsyncHandler = AsyncHandler(AsyncLocator.getAsyncHandlerForTest(_target));
+			cleanupAsync();
 
 			var test:TestInfo = _collectedTests[_currentIndex];
-			var timeout:int = getTimeout(test);
-			_failures = true;
-			_notifier.fireTestFailure(new Failure(test.description, new AssertionError("Test did not complete within specified timeout " + timeout + "ms")));
+			if(asyncHandler.error)
+			{
+				_failures = true;
+				_notifier.fireTestFailure(new Failure(test.description, asyncHandler.error));
+			}
+			if(asyncHandler.pendingCount > 0)
+			{
+				_failures = true;
+				_notifier.fireTestFailure(new Failure(test.description, new AssertionError("Test did not complete within specified timeout " + test.asyncTimeout + "ms")));
+			}
 			
-			_notifier.fireTestFinished(test.description);
-			_currentIndex++;
-
+			afterTest(test);
 			continueAll();
 		}
 	}
 }
 
+import org.apache.royale.events.Event;
+import org.apache.royale.test.async.IAsyncHandler;
+import org.apache.royale.test.runners.notification.Failure;
+import org.apache.royale.utils.Timer;
+
 class TestInfo
 {
-	public function TestInfo(name:String, reference:Function, ignore:Boolean, async:Boolean)
+	public function TestInfo(name:String, reference:Function, ignore:Boolean, asyncTimeout:int)
 	{
 		this.description = name;
 		this.reference = reference;
 		this.ignore = ignore;
-		this.async = async;
+		this.asyncTimeout = asyncTimeout;
 	}
 
 	public var description:String;
 	public var reference:Function;
 	public var ignore:Boolean;
-	public var async:Boolean;
+	public var asyncTimeout:int;
+}
+
+class AsyncHandler implements IAsyncHandler
+{
+	private var _bodyExecuting:Boolean = false;
+
+	public function get bodyExecuting():Boolean
+	{
+		return _bodyExecuting;
+	}
+
+	public function set bodyExecuting(value:Boolean):void
+	{
+		_bodyExecuting = value;
+	}
+
+	private var _error:Error = null;
+
+	public function get error():Error
+	{
+		return _error;
+	}
+
+	public function get pendingCount():int
+	{
+		return _timers.length;
+	}
+
+	private var _timers:Vector.<Timer> = new <Timer>[];
+
+	public function asyncHandler(callback:Function, delayMS:int):void
+	{
+		var timerIndex:int = -1;
+		var timer:Timer = new Timer(delayMS, 1);
+		timer.addEventListener(Timer.TIMER, function(event:Event):void
+		{
+			timer.removeEventListener(Timer.TIMER, arguments["callee"]);
+			var index:int = _timers.indexOf(timer);
+			if(index !== -1)
+			{
+				_timers.splice(index, 1);
+			}
+			try
+			{
+				callback();
+			}
+			catch(error:Error)
+			{
+				_error = error;
+			}
+		});
+		_timers.push(timer);
+		timer.start();
+	}
 }
\ No newline at end of file
diff --git a/frameworks/projects/RoyaleUnit/src/test/royale/tests/AsyncTests.as b/frameworks/projects/RoyaleUnit/src/test/royale/tests/AsyncTests.as
index 4ff89df..3d817c7 100644
--- a/frameworks/projects/RoyaleUnit/src/test/royale/tests/AsyncTests.as
+++ b/frameworks/projects/RoyaleUnit/src/test/royale/tests/AsyncTests.as
@@ -22,6 +22,7 @@ package tests
 	import org.apache.royale.test.runners.MetadataRunner;
 	import org.apache.royale.test.runners.notification.RunNotifier;
 	import org.apache.royale.test.runners.notification.IRunListener;
+	import org.apache.royale.test.async.Async;
 
 	public class AsyncTests
 	{
@@ -48,18 +49,75 @@ package tests
 			_runner.run(notifier);
 			Assert.assertTrue(listener.runStarted);
 			Assert.assertFalse(listener.runFinished);
-			Assert.assertStrictlyEquals(listener.finishedCount, 1);
+			//we can't check finished count because the order of tests cannot be
+			//determined. the async test may be run first.
 			Assert.assertStrictlyEquals(listener.failureCount, 0);
 			Assert.assertStrictlyEquals(listener.ignoreCount, 0);
-			Assert.assertEquals(listener.active, "tests.AsyncTests::AsyncFixture.test2");
+			Assert.assertTrue(listener.active.indexOf("AsyncTests::AsyncFixture.test2") > 0);
 			_runner.pleaseStop();
 		}
+
+		[Test(async)]
+		public function testRunFinishesAsynchronously():void
+		{
+			var notifier:RunNotifier = new RunNotifier();
+			var listener:AsyncListener = new AsyncListener();
+			notifier.addListener(listener);
+			_runner = new MetadataRunner(AsyncFixture);
+			_runner.run(notifier);
+			Async.delayCall(this, function():void
+			{
+				Assert.assertTrue(listener.runStarted);
+				Assert.assertTrue(listener.runFinished);
+				Assert.assertStrictlyEquals(listener.finishedCount, 2);
+				Assert.assertStrictlyEquals(listener.failureCount, 0);
+				Assert.assertStrictlyEquals(listener.ignoreCount, 0);
+				Assert.assertNull(listener.active);
+			}, 400);
+		}
+
+		[Test(async)]
+		public function testAsyncTestWithSynchronousFail():void
+		{
+			var notifier:RunNotifier = new RunNotifier();
+			var listener:AsyncListener = new AsyncListener();
+			notifier.addListener(listener);
+			_runner = new MetadataRunner(AsyncFailFixture);
+			_runner.run(notifier);
+			Assert.assertTrue(listener.runStarted);
+			Assert.assertTrue(listener.runFinished);
+			Assert.assertStrictlyEquals(listener.finishedCount, 1);
+			Assert.assertStrictlyEquals(listener.failureCount, 1);
+			Assert.assertStrictlyEquals(listener.ignoreCount, 0);
+			Assert.assertNull(listener.active);
+		}
+
+		[Test(async)]
+		public function testAsyncTestWithFailInsideDelayCall():void
+		{
+			var notifier:RunNotifier = new RunNotifier();
+			var listener:AsyncListener = new AsyncListener();
+			notifier.addListener(listener);
+			_runner = new MetadataRunner(AsyncFailWithDelayCallFixture);
+			_runner.run(notifier);
+			Async.delayCall(this, function():void
+			{
+				Assert.assertTrue(listener.runStarted);
+				Assert.assertTrue(listener.runFinished);
+				Assert.assertStrictlyEquals(listener.finishedCount, 1);
+				Assert.assertStrictlyEquals(listener.failureCount, 1);
+				Assert.assertStrictlyEquals(listener.ignoreCount, 0);
+				Assert.assertNull(listener.active);
+			}, 400);
+		}
 	}
 }
 
 import org.apache.royale.test.runners.notification.IRunListener;
 import org.apache.royale.test.runners.notification.Failure;
 import org.apache.royale.test.runners.notification.Result;
+import org.apache.royale.test.Assert;
+import org.apache.royale.test.async.Async;
 
 class AsyncFixture
 {
@@ -68,12 +126,33 @@ class AsyncFixture
 	{
 	}
 
-	[Test(async)]
+	[Test(async,timeout="100")]
 	public function test2():void
 	{
 	}
 }
 
+class AsyncFailFixture
+{
+	[Test(async,timeout="100")]
+	public function test1():void
+	{
+		Assert.fail();
+	}
+}
+
+class AsyncFailWithDelayCallFixture
+{
+	[Test(async,timeout="200")]
+	public function test1():void
+	{
+		Async.delayCall(this, function():void
+		{
+			Assert.fail();
+		}, 100);
+	}
+}
+
 class AsyncListener implements IRunListener
 {
 	public var runStarted:Boolean = false;