From 6de157612b825bd155da0939cc1c0ae43f873902 Mon Sep 17 00:00:00 2001
From: Joshua Granick
Date: Thu, 4 Apr 2019 11:11:40 -0700
Subject: [PATCH 1/2] DataProvider support, async fixes, headless AIR output
(merges OpenFL and TiVo changes #58, #159, #161)
---
src/massive/munit/TestClassHelper.hx | 128 +++++++--
src/massive/munit/TestResult.hx | 19 +-
src/massive/munit/TestRunner.hx | 250 ++++++++++++------
src/massive/munit/async/AsyncDelegate.hx | 50 +++-
src/massive/munit/async/AsyncFactory.hx | 8 +-
.../async/UnexpectedAsyncDelegateException.hx | 20 ++
src/massive/munit/client/PrintClient.hx | 24 +-
src/massive/munit/client/PrintClientBase.hx | 12 +-
src/massive/munit/client/RichPrintClient.hx | 12 +
test/TestSuite.hx | 38 +--
test/massive/munit/DataProviderTest.hx | 147 ++++++++++
.../massive/munit/DataProviderUsageExample.hx | 92 +++++++
.../massive/munit/PrePostActionAssertsTest.hx | 63 +++++
test/massive/munit/TestClassHelperTest.hx | 17 +-
test/massive/munit/TestClassStub.hx | 8 +-
test/massive/munit/TestClassStubSuper.hx | 55 ++++
test/massive/munit/async/AsyncDelegateTest.hx | 2 +-
test/massive/munit/async/ChainedAsyncTest.hx | 211 +++++++++++++++
test/massive/munit/async/ParallelAsyncTest.hx | 149 +++++++++++
tool/template/test-main.mtt | 4 +-
20 files changed, 1157 insertions(+), 152 deletions(-)
create mode 100644 src/massive/munit/async/UnexpectedAsyncDelegateException.hx
create mode 100644 test/massive/munit/DataProviderTest.hx
create mode 100644 test/massive/munit/DataProviderUsageExample.hx
create mode 100644 test/massive/munit/PrePostActionAssertsTest.hx
create mode 100644 test/massive/munit/TestClassStubSuper.hx
create mode 100644 test/massive/munit/async/ChainedAsyncTest.hx
create mode 100644 test/massive/munit/async/ParallelAsyncTest.hx
diff --git a/src/massive/munit/TestClassHelper.hx b/src/massive/munit/TestClassHelper.hx
index 655f217..1ac22b1 100644
--- a/src/massive/munit/TestClassHelper.hx
+++ b/src/massive/munit/TestClassHelper.hx
@@ -94,6 +94,18 @@ class TestClassHelper
*/
public inline static var META_TAG_TEST_DEBUG:String = "TestDebug";
+ /**
+ * Meta tag marking a test as having an argument data provider
+ */
+ public inline static var META_TAG_DATA_PROVIDER:String = "DataProvider";
+
+ /**
+ * Pseudo meta tag marking method inheritance depth. Auto generated
+ * by test parser, thus not in META_TAGS below as it is never expected
+ * to be in a test file.
+ **/
+ public inline static var META_TAG_INHERITANCE_DEPTH:String = "InheritanceDepth";
+
/**
* Array of all valid meta tags.
*/
@@ -116,24 +128,24 @@ class TestClassHelper
public var test(default, null):Dynamic;
/**
- * The life cycle method to be called once, before tests in the class are executed.
+ * The life cycle methods to be called once, before tests in the class are executed.
*/
- public var beforeClass(default, null):Function;
+ public var beforeClass(default, null):Array;
/**
- * The life cycle method to be called once, after tests in the class are executed.
+ * The life cycle methods to be called once, after tests in the class are executed.
*/
- public var afterClass(default, null):Function;
+ public var afterClass(default, null):Array;
/**
- * The life cycle method to be called once, before each test in the class is executed.
+ * The life cycle methods to be called once, before each test in the class is executed.
*/
- public var before(default, null):Function;
+ public var before(default, null):Array;
/**
- * The life cycle method to be called once, after each test in the class is executed.
+ * The life cycle methods to be called once, after each test in the class is executed.
*/
- public var after(default, null):Function;
+ public var after(default, null):Array;
public var className(default, null):String;
var tests:Array = [];
@@ -150,10 +162,10 @@ class TestClassHelper
this.type = type;
this.isDebug = isDebug;
className = Type.getClassName(type);
- beforeClass = nullFunc;
- afterClass = nullFunc;
- before = nullFunc;
- after = nullFunc;
+ beforeClass = new Array();
+ afterClass = new Array();
+ before = new Array();
+ after = new Array();
parse(type);
}
@@ -195,6 +207,9 @@ class TestClassHelper
var fieldMeta = collateFieldMeta(inherintanceChain);
scanForTests(fieldMeta);
tests.sort(sortTestsByName); // not pc as allows for possible test dependencies but useful for report consistency
+ // after methods should be called from subclass to base class order
+ after.reverse();
+ afterClass.reverse();
}
function getInheritanceChain(clazz:Class):Array>
@@ -208,10 +223,18 @@ class TestClassHelper
function collateFieldMeta(inherintanceChain:Array>):Dynamic
{
var meta = {};
+ var depth = -1; // initially negative since incremented at top of loop
var i = inherintanceChain.length;
while (i-- > 0)
{
var clazz = inherintanceChain[i]; // start at root
+ // go to next inheritance depth
+ depth++;
+ // update lifecycle function arrays with new depth
+ beforeClass.push(nullFunc);
+ afterClass.push(nullFunc);
+ before.push(nullFunc);
+ after.push(nullFunc);
var newMeta = Meta.getFields(clazz);
var markedFieldNames = Reflect.fields(newMeta);
@@ -229,7 +252,9 @@ class TestClassHelper
var tagsCopy = {};
for (tagName in newTagNames)
Reflect.setField(tagsCopy, tagName, Reflect.field(newFieldTags, tagName));
-
+ // remember the inheritance depth of this field with a pseudo-tag
+ Reflect.setField(tagsCopy, META_TAG_INHERITANCE_DEPTH, [depth]);
+
Reflect.setField(meta, fieldName, tagsCopy);
}
else
@@ -251,6 +276,9 @@ class TestClassHelper
var tagValue = Reflect.field(newFieldTags, tagName);
Reflect.setField(recordedFieldTags, tagName, tagValue);
}
+ // update the inheritance depth of this field, as it overrides the
+ // earlier definition
+ Reflect.setField(recordedFieldTags, META_TAG_INHERITANCE_DEPTH, [depth]);
}
}
}
@@ -271,6 +299,7 @@ class TestClassHelper
function searchForMatchingTags(fieldName:String, func:Dynamic, funcMeta:Dynamic)
{
+ var depth = Reflect.field(funcMeta, META_TAG_INHERITANCE_DEPTH)[0];
for (tag in META_TAGS)
{
if (!Reflect.hasField(funcMeta, tag)) continue;
@@ -278,6 +307,8 @@ class TestClassHelper
var description = (args != null) ? args[0] : "";
var isAsync = (args != null && description == META_PARAM_ASYNC_TEST); // deprecated support for @Test("Async")
var isIgnored = Reflect.hasField(funcMeta, META_TAG_IGNORE);
+ var hasDataProvider = Reflect.hasField(funcMeta, META_TAG_DATA_PROVIDER);
+ var dataProvider:String = null;
if (isAsync)
{
@@ -289,28 +320,69 @@ class TestClassHelper
description = (args != null) ? args[0] : "";
}
+ if (hasDataProvider)
+ {
+ args = Reflect.field(funcMeta, META_TAG_DATA_PROVIDER);
+ if (args != null)
+ {
+ dataProvider = args[0];
+ }
+ else
+ {
+ throw new MUnitException("Missing dataProvider source", null);
+ }
+ }
+
switch(tag)
{
- case META_TAG_BEFORE_CLASS: beforeClass = func;
- case META_TAG_AFTER_CLASS: afterClass = func;
- case META_TAG_BEFORE: before = func;
- case META_TAG_AFTER: after = func;
- case META_TAG_ASYNC_TEST: if(!isDebug) addTest(fieldName, func, test, true, isIgnored, description);
- case META_TAG_TEST: if(!isDebug) addTest(fieldName, func, test, isAsync, isIgnored, description);
- case META_TAG_TEST_DEBUG: if(isDebug) addTest(fieldName, func, test, isAsync, isIgnored, description);
+ case META_TAG_BEFORE_CLASS: beforeClass[depth] = func;
+ case META_TAG_AFTER_CLASS: afterClass[depth] = func;
+ case META_TAG_BEFORE: before[depth] = func;
+ case META_TAG_AFTER: after[depth] = func;
+ case META_TAG_ASYNC_TEST: if(!isDebug) addTest(fieldName, func, test, true, isIgnored, description, dataProvider);
+ case META_TAG_TEST: if(!isDebug) addTest(fieldName, func, test, isAsync, isIgnored, description, dataProvider);
+ case META_TAG_TEST_DEBUG: if(isDebug) addTest(fieldName, func, test, isAsync, isIgnored, description, dataProvider);
}
}
}
- function addTest(field:String, testFunction:Function, testInstance:Dynamic, isAsync:Bool, isIgnored:Bool, description:String)
+ function addTest(field:String, testFunction:Function, testInstance:Dynamic, isAsync:Bool, isIgnored:Bool, description:String, dataProvider:String)
{
- var result:TestResult = new TestResult();
- result.async = isAsync;
- result.ignore = isIgnored;
- result.className = className;
- result.description = description;
- result.name = field;
- tests.push({scope:testInstance, test:testFunction, result:result});
+ var argsData:Array> = [[]];
+ if (dataProvider != null)
+ {
+ // look for object instance field
+ var provider:Dynamic = Reflect.field(testInstance, dataProvider);
+ if (null == provider) {
+ // look for static class field
+ provider = Reflect.field(Type.getClass(testInstance), dataProvider);
+ }
+ if (Reflect.isFunction(provider))
+ {
+ provider = Reflect.callMethod(testInstance, provider, []);
+ }
+ if (Std.is(provider, Array))
+ {
+ argsData = cast provider;
+ }
+ else
+ {
+ throw new MUnitException("dataProvider \'" + dataProvider +
+ "\' did not provide args array", null);
+ }
+ }
+ for (args in argsData)
+ {
+ var result:TestResult = new TestResult();
+ result.async = isAsync;
+ result.ignore = isIgnored;
+ result.className = className;
+ result.description = description;
+ result.name = field;
+ result.args = args;
+ var data:TestCaseData = { test:testFunction, scope:testInstance, result:result };
+ tests.push(data);
+ }
}
function sortTestsByName(x:TestCaseData, y:TestCaseData):Int
diff --git a/src/massive/munit/TestResult.hx b/src/massive/munit/TestResult.hx
index a6f764e..5a285ec 100644
--- a/src/massive/munit/TestResult.hx
+++ b/src/massive/munit/TestResult.hx
@@ -82,6 +82,11 @@ class TestResult
*/
public var ignore:Bool = false;
+ /**
+ * Arguments for the test, or null if no args
+ **/
+ public var args:Array;
+
/**
* If this test failed, the assertion exception that was captured.
*/
@@ -97,7 +102,19 @@ class TestResult
/**
* Class constructor.
*/
- public function new() {}
+ public function new()
+ {
+ passed = false;
+ executionTime = 0.0;
+ name = "";
+ className = "";
+ description = "";
+ async = false;
+ ignore = false;
+ args = null;
+ error = null;
+ failure = null;
+ }
function get_type():TestResultType
{
diff --git a/src/massive/munit/TestRunner.hx b/src/massive/munit/TestRunner.hx
index b567640..a2bcadb 100644
--- a/src/massive/munit/TestRunner.hx
+++ b/src/massive/munit/TestRunner.hx
@@ -35,7 +35,9 @@ import massive.munit.async.AsyncFactory;
import massive.munit.async.AsyncTimeoutException;
import massive.munit.async.IAsyncDelegateObserver;
import massive.munit.async.MissingAsyncDelegateException;
+import massive.munit.async.UnexpectedAsyncDelegateException;
import massive.munit.util.Timer;
+import massive.munit.TestResult;
#if neko
import neko.vm.Thread;
@@ -45,6 +47,14 @@ import cpp.vm.Thread;
import java.vm.Thread;
#end
+using Lambda;
+#if haxe3
+import haxe.CallStack;
+#else
+import haxe.Stack;
+private typedef CallStack = Stack;
+#end
+
/**
* Runner used to execute one or more suites of unit tests.
*
@@ -88,6 +98,11 @@ class TestRunner implements IAsyncDelegateObserver
{
static var emptyParams:Array = [];
+ /**
+ * The currently active TestRunner. Will be null if no test is executing.
+ **/
+ public static var activeRunner(default, null):TestRunner;
+
/**
* Handler called when all tests have been executed and all clients
* have completed processing the results.
@@ -107,8 +122,7 @@ class TestRunner implements IAsyncDelegateObserver
var clients:Array = [];
var activeHelper:TestClassHelper;
var testSuites:Array;
- var asyncPending:Bool;
- var asyncDelegate:AsyncDelegate;
+ var asyncDelegates:Array; // array to support multiple async handlers (chaining, or simultaneous)
var suiteIndex:Int;
public var asyncFactory(default, set):AsyncFactory;
@@ -172,8 +186,7 @@ class TestRunner implements IAsyncDelegateObserver
{
if (running) return;
running = true;
- asyncPending = false;
- asyncDelegate = null;
+ activeRunner = this;
testCount = 0;
failCount = 0;
errorCount = 0;
@@ -182,29 +195,88 @@ class TestRunner implements IAsyncDelegateObserver
suiteIndex = 0;
clientCompleteCount = 0;
Assert.assertionCount = 0; // don't really like this static but can't see way around it atm. ms 17/12/10
- testSuites = [for(suiteType in testSuiteClasses) Type.createInstance(suiteType, emptyParams)];
+ emptyParams = new Array();
+ asyncDelegates = new Array();
+ testSuites = new Array();
startTime = Timer.stamp();
- #if (neko || cpp || java)
- var self = this;
- var runThread:Thread = Thread.create(function()
- {
- self.execute();
- while (self.running)
- {
- Sys.sleep(.2);
- }
- var mainThead:Thread = Thread.readMessage(true);
- mainThead.sendMessage("done");
- });
- runThread.sendMessage(Thread.current());
- Thread.readMessage(true);
+ for (suiteType in testSuiteClasses)
+ {
+ testSuites.push(Type.createInstance(suiteType, new Array()));
+ }
+
+ #if (!lime && !nme && (neko||cpp))
+ var self = this;
+ var runThread:Thread = Thread.create(function()
+ {
+ self.execute();
+ while (self.running)
+ {
+ Sys.sleep(.2);
+ }
+ var mainThead:Thread = Thread.readMessage(true);
+ mainThead.sendMessage("done");
+ });
+
+ runThread.sendMessage(Thread.current());
+ Thread.readMessage(true);
#else
execute();
#end
}
- function execute()
+ private function callHelperMethod ( method:Dynamic ):Void
+ {
+ try
+ {
+ /*
+ Wrapping in try/catch solves below problem:
+ If @BeforeClass, @AfterClass, @Before, @After methods
+ have any Assert calls that fail, and if they are not
+ caught and handled here ... then TestRunner stalls.
+ */
+ Reflect.callMethod(activeHelper.test, method, emptyParams);
+ }
+ catch (e:Dynamic)
+ {
+ var testcaseData: Dynamic = activeHelper.current(); // fetch the test context
+ exceptionHandler ( e, testcaseData.result );
+ }
+ }
+
+
+ private inline function exceptionHandler ( e:Dynamic, result:TestResult ):Void
+ {
+ #if hamcrest
+ if (Std.is(e, org.hamcrest.AssertionException))
+ {
+ e = new AssertionException(e.message, e.info);
+ }
+ #end
+
+ result.executionTime = Timer.stamp() - testStartTime;
+
+ if (Std.is(e, AssertionException))
+ {
+ result.failure = e;
+ failCount++;
+ for (c in clients)
+ c.addFail(result);
+ }
+ else
+ {
+ if (!Std.is(e, MUnitException))
+ e = new UnhandledException(e, result.location);
+
+ result.error = e;
+ errorCount++;
+ for (c in clients)
+ c.addError(result);
+ }
+ }
+
+
+ private function execute():Void
{
for (i in suiteIndex...testSuites.length)
{
@@ -214,10 +286,13 @@ class TestRunner implements IAsyncDelegateObserver
if (activeHelper == null || activeHelper.type != testClass)
{
activeHelper = new TestClassHelper(testClass, isDebug);
- tryCallMethod(activeHelper.test, activeHelper.beforeClass, emptyParams);
+ activeHelper.beforeClass.iter(callHelperMethod);
}
executeTestCases();
- if(!asyncPending) tryCallMethod(activeHelper.test, activeHelper.afterClass, emptyParams);
+ if ( ! isAsyncPending() )
+ {
+ activeHelper.afterClass.iter(callHelperMethod);
+ }
else
{
suite.repeat();
@@ -228,7 +303,7 @@ class TestRunner implements IAsyncDelegateObserver
testSuites[i] = null;
}
- if (!asyncPending)
+ if ( ! isAsyncPending() )
{
var time:Float = Timer.stamp() - startTime;
for (client in clients)
@@ -264,34 +339,51 @@ class TestRunner implements IAsyncDelegateObserver
else
{
testCount++; // note we don't include ignored in final test count
- tryCallMethod(activeHelper.test, activeHelper.before, emptyParams);
+ activeHelper.before.iter(callHelperMethod);
testStartTime = Timer.stamp();
executeTestCase(testCaseData, testCaseData.result.async);
- if(!asyncPending) tryCallMethod(activeHelper.test, activeHelper.after, emptyParams);
- else break;
+
+ if ( ! isAsyncPending() ) {
+ activeRunner = null; // for SYNC tests: resetting this here instead of clientCompletionHandler
+ activeHelper.after.iter(callHelperMethod);
+ }
+ else
+ break;
}
}
}
- function executeTestCase(testCaseData:Dynamic, async:Bool)
+ function executeTestCase(testCaseData:Dynamic, async:Bool):Void
{
var result:TestResult = testCaseData.result;
try
{
var assertionCount:Int = Assert.assertionCount;
+
+ // This was being reset to null when testing TestRunner itself i.e. testing munit using munit.
+ // By setting this here, this runner value will be valid right when tests (Sync/ASync) are about to run.
+ activeRunner = this;
+
if (async)
{
- Reflect.callMethod(testCaseData.scope, testCaseData.test, [asyncFactory]);
- if(asyncDelegate == null)
+ var args:Array = [asyncFactory];
+ if (result.args != null) args = args.concat(result.args);
+
+ var delegateCount = asyncFactory.asyncDelegateCount;
+ Reflect.callMethod(testCaseData.scope, testCaseData.test, args);
+
+ if(asyncFactory.asyncDelegateCount <= delegateCount)
{
throw new MissingAsyncDelegateException("No AsyncDelegate was created in async test at " + result.location, null);
}
- asyncPending = true;
}
else
{
- Reflect.callMethod(testCaseData.scope, testCaseData.test, emptyParams);
+ Reflect.callMethod(testCaseData.scope, testCaseData.test, result.args);
+ }
+ if (! isAsyncPending())
+ {
result.passed = true;
result.executionTime = Timer.stamp() - testStartTime;
passCount++;
@@ -301,36 +393,8 @@ class TestRunner implements IAsyncDelegateObserver
}
catch(e:Dynamic)
{
- if(async && asyncDelegate != null)
- {
- asyncDelegate.cancelTest();
- asyncDelegate = null;
- }
-
- #if hamcrest
- if (Std.is(e, org.hamcrest.AssertionException))
- e = new AssertionException(e.message, e.info);
- #end
-
- if (Std.is(e, AssertionException))
- {
- result.executionTime = Timer.stamp() - testStartTime;
- result.failure = e;
- failCount++;
- for (c in clients)
- c.addFail(result);
- }
- else
- {
- result.executionTime = Timer.stamp() - testStartTime;
- if (!Std.is(e, MUnitException))
- e = new UnhandledException(e, result.location);
-
- result.error = e;
- errorCount++;
- for (c in clients)
- c.addError(result);
- }
+ cancelAllPendingAsyncTests();
+ exceptionHandler ( e, result );
}
}
@@ -351,13 +415,16 @@ class TestRunner implements IAsyncDelegateObserver
public function asyncResponseHandler(delegate:AsyncDelegate)
{
var testCaseData = activeHelper.current();
- testCaseData.scope = delegate;
testCaseData.test = delegate.runTest;
- asyncPending = false;
- asyncDelegate = null;
+ testCaseData.scope = delegate;
+
+ asyncDelegates.remove(delegate);
executeTestCase(testCaseData, false);
- tryCallMethod(activeHelper.test, activeHelper.after, emptyParams);
- execute();
+ if ( ! isAsyncPending() ) {
+ activeRunner = null; // for ASync regular cases: resetting this here instead of clientCompletionHandler
+ activeHelper.after.iter(callHelperMethod);
+ execute();
+ }
}
/**
@@ -368,21 +435,36 @@ class TestRunner implements IAsyncDelegateObserver
*/
public function asyncTimeoutHandler(delegate:AsyncDelegate)
{
- var testCaseData = activeHelper.current();
- var result = testCaseData.result;
- result.executionTime = Timer.stamp() - testStartTime;
- result.error = new AsyncTimeoutException("", delegate.info);
- asyncPending = false;
- asyncDelegate = null;
- errorCount++;
- for (c in clients) c.addError(result);
- tryCallMethod(activeHelper.test, activeHelper.after, emptyParams);
- execute();
+ var testCaseData:Dynamic = activeHelper.current();
+ asyncDelegates.remove(delegate);
+
+ if (delegate.hasTimeoutHandler)
+ {
+ testCaseData.test = delegate.runTimeout;
+ testCaseData.scope = delegate;
+ executeTestCase(testCaseData, false);
+ }
+ else
+ {
+ cancelAllPendingAsyncTests();
+
+ var result:TestResult = testCaseData.result;
+ result.executionTime = Timer.stamp() - testStartTime;
+ result.error = new AsyncTimeoutException("", delegate.info);
+
+ errorCount++;
+ for (c in clients) c.addError(result);
+ }
+ if ( ! isAsyncPending() ) {
+ activeRunner = null; // for ASync Time-out cases: resetting this here instead of clientCompletionHandler
+ activeHelper.after.iter(callHelperMethod);
+ execute();
+ }
}
public function asyncDelegateCreatedHandler(delegate:AsyncDelegate)
{
- asyncDelegate = delegate;
+ asyncDelegates.push(delegate);
}
inline function createAsyncFactory():AsyncFactory return new AsyncFactory(this);
@@ -391,4 +473,18 @@ class TestRunner implements IAsyncDelegateObserver
if(Reflect.compareMethods(func, TestClassHelper.nullFunc)) return null;
return Reflect.callMethod(o, func, args);
}
+
+ private inline function isAsyncPending() : Bool
+ {
+ return (asyncDelegates.length > 0);
+ }
+
+ private function cancelAllPendingAsyncTests() : Void
+ {
+ for (delegate in asyncDelegates)
+ {
+ delegate.cancelTest();
+ asyncDelegates.remove(delegate);
+ }
+ }
}
diff --git a/src/massive/munit/async/AsyncDelegate.hx b/src/massive/munit/async/AsyncDelegate.hx
index d678919..979526d 100644
--- a/src/massive/munit/async/AsyncDelegate.hx
+++ b/src/massive/munit/async/AsyncDelegate.hx
@@ -64,7 +64,7 @@ class AsyncDelegate
* it is ready for the remainder of the async test to be run.
*
*/
- public var delegateHandler(default, null):Dynamic;
+ public var delegateHandler(default, null):Function;
public var timeoutDelay(default, null):Int;
public var timedOut(default, null):Bool = false;
public var canceled(default, null):Bool = false;
@@ -72,6 +72,9 @@ class AsyncDelegate
var handler:Function;
var timer:Timer;
var deferredTimer:Timer;
+ var timeoutHandler:Function;
+
+ public var hasTimeoutHandler(get, never):Bool;
/**
* An array of values to be passed as parameters to the test class handler.
@@ -85,17 +88,22 @@ class AsyncDelegate
* @param testCase test case instance where the async test originated
* @param handler the handler in the test case for a successful async response
* @param ?timeout [optional] number of milliseconds to wait before timing out. Defaults to 400
+ * @param ?timeoutHandler [optional] a handler that is called if there is no async response.
+ * If none provided, the test will be marked failing if the timeout
+ * expires. Otherwise, it is up to the handler to determine if the
+ * test failed.
* @param ?info [optional] pos infos of the test which requests an instance of this delegate
*/
- public function new(testCase:Dynamic, handler:Function, ?timeout:Int, ?info:PosInfos)
+ public function new(testCase:Dynamic, handler:Function, ?timeout:Int, ?timeoutHandler:Function, ?info:PosInfos)
{
this.testCase = testCase;
this.handler = handler;
+ this.timeoutHandler = timeoutHandler;
this.delegateHandler = Reflect.makeVarArgs(responseHandler);
this.info = info;
if (timeout == null || timeout <= 0) timeout = DEFAULT_TIMEOUT;
timeoutDelay = timeout;
- timer = Timer.delay(timeoutHandler, timeoutDelay);
+ timer = Timer.delay(noResponseHandler, timeoutDelay);
}
/**
@@ -107,6 +115,30 @@ class AsyncDelegate
Reflect.callMethod(testCase, handler, params);
}
+ /**
+ * Return true if there is a timeout handler
+ **/
+ private function get_hasTimeoutHandler():Bool
+ {
+ return timeoutHandler != null;
+ }
+
+ /**
+ * Execute the timeout handler for the asynchronous test. This should be called by
+ * the observer when it has been informed there is a timeout *only* when hasTimeoutHandler
+ * returns true.
+ **/
+ public function runTimeout():Void
+ {
+ if (canceled) return;
+
+ if (timeoutHandler == null)
+ {
+ throw new MUnitException("Observer attempted to run timeout handler when none set!");
+ }
+ Reflect.callMethod(testCase, timeoutHandler, []);
+ }
+
/**
* Cancels pending async timeout.
*/
@@ -134,17 +166,17 @@ class AsyncDelegate
observer = null;
}
- function timeoutHandler()
+ function noResponseHandler():Void
{
- #if flash
- //pushing timeout onto next frame to prevent raxe condition bug when flash framerate drops too low and timeout timer executes prior to response on same frame
- deferredTimer = Timer.delay(actualTimeoutHandler, 1);
+ #if (flash && !air)
+ //pushing timeout onto next frame to prevent race condition bug when flash framerate drops too low and timeout timer executes prior to response on same frame
+ deferredTimer = Timer.delay(actualNoResponseHandler, 1);
#else
- actualTimeoutHandler();
+ actualNoResponseHandler();
#end
}
- function actualTimeoutHandler()
+ function actualNoResponseHandler()
{
deferredTimer = null;
handler = null;
diff --git a/src/massive/munit/async/AsyncFactory.hx b/src/massive/munit/async/AsyncFactory.hx
index d479aac..a189ddd 100644
--- a/src/massive/munit/async/AsyncFactory.hx
+++ b/src/massive/munit/async/AsyncFactory.hx
@@ -85,12 +85,16 @@ class AsyncFactory
* @param testCase test case instance where the async test originated
* @param handler the handler in the test case for a successful async response
* @param ?timeout [optional] number of milliseconds to wait before timing out
+ * @param ?timeoutHandler [optional] a handler that is called if there is no async response.
+ * If none provided, the test will be marked failing if the timeout
+ * expires. Otherwise, it is up to the handler to determine if the
+ * test failed.
* @param ?info [optional] pos infos of the test which requests an instance of this delegate
* @return a delegate function for handling the asynchronous response from an async test case
*/
- public function createHandler(testCase:Dynamic, handler:Function, ?timeout:Int, ?info:PosInfos):Dynamic
+ public function createHandler(testCase:Dynamic, handler:Function, ?timeout:Int, ?timeoutHandler:Function, ?info:PosInfos):Dynamic
{
- var delegate:AsyncDelegate = new AsyncDelegate(testCase, handler, timeout, info);
+ var delegate:AsyncDelegate = new AsyncDelegate(testCase, handler, timeout, timeoutHandler, info);
delegate.observer = observer;
asyncDelegateCount++;
observer.asyncDelegateCreatedHandler(delegate);
diff --git a/src/massive/munit/async/UnexpectedAsyncDelegateException.hx b/src/massive/munit/async/UnexpectedAsyncDelegateException.hx
new file mode 100644
index 0000000..1bbb87f
--- /dev/null
+++ b/src/massive/munit/async/UnexpectedAsyncDelegateException.hx
@@ -0,0 +1,20 @@
+package massive.munit.async;
+
+import haxe.PosInfos;
+import massive.munit.MUnitException;
+import massive.haxe.util.ReflectUtil;
+
+/**
+ * Exception thrown when a synchronous test creates an AsyncDelegate.
+ */
+class UnexpectedAsyncDelegateException extends MUnitException
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function new(message:String, ?info:PosInfos)
+ {
+ super(message, info);
+ type = ReflectUtil.here().className;
+ }
+}
diff --git a/src/massive/munit/client/PrintClient.hx b/src/massive/munit/client/PrintClient.hx
index 868b4b6..2d9c824 100644
--- a/src/massive/munit/client/PrintClient.hx
+++ b/src/massive/munit/client/PrintClient.hx
@@ -58,7 +58,7 @@ class PrintClient extends PrintClientBase
*/
public static inline var DEFAULT_ID:String = "print";
- #if (js || flash)
+ #if (js || (flash && !air))
var external:ExternalPrintClient;
#if flash
var textField:flash.text.TextField;
@@ -78,7 +78,7 @@ class PrintClient extends PrintClientBase
super.init();
#if nodejs
- #elseif (js || flash)
+ #elseif (js || (flash && !air))
external = new ExternalPrintClientJS();
#if flash
initFlash();
@@ -91,7 +91,7 @@ class PrintClient extends PrintClientBase
haxe.Log.trace = customTrace;
}
- #if flash
+ #if (flash && !air)
function initFlash()
{
if(!flash.external.ExternalInterface.available)
@@ -128,7 +128,7 @@ class PrintClient extends PrintClientBase
super.printOverallResult(result);
#if nodejs
- #elseif (js || flash)
+ #elseif (js || (flash && !air))
external.setResult(result);
external.setResultBackground(result);
#end
@@ -139,11 +139,15 @@ class PrintClient extends PrintClientBase
addTrace(value, info);
}
+ #if (flash && air)
+ private var flashOutput = "";
+ #end
+
override public function print(value:Dynamic)
{
super.print(value);
- #if flash
+ #if (flash && !air)
textField.appendText(value);
textField.scrollV = textField.maxScrollV;
#end
@@ -152,8 +156,16 @@ class PrintClient extends PrintClientBase
untyped process.stdout.write(value);
#elseif (neko || cpp || java || cs || python || php || hl || eval)
Sys.print(value);
- #elseif (js || flash)
+ #elseif (js || (flash && !air))
external.print(value);
+ #elseif (flash && air)
+ flashOutput += value;
+ while (flashOutput.indexOf("\n") > -1)
+ {
+ var index = flashOutput.indexOf("\n") + 1;
+ flash.Lib.trace(flashOutput.substr(0, index));
+ flashOutput = flashOutput.substr(index);
+ }
#end
}
}
\ No newline at end of file
diff --git a/src/massive/munit/client/PrintClientBase.hx b/src/massive/munit/client/PrintClientBase.hx
index 5cda00c..8b540d4 100644
--- a/src/massive/munit/client/PrintClientBase.hx
+++ b/src/massive/munit/client/PrintClientBase.hx
@@ -89,8 +89,8 @@ class PrintClientBase extends AbstractTestResultClient
{
switch(result.type)
{
- case ERROR: printLine("ERROR: " + Std.string(result.error), 1);
- case FAIL: printLine("FAIL: " + Std.string(result.failure), 1);
+ case ERROR: printLine("ERROR: " + result.location + " - "+ Std.string(result.error), 1);
+ case FAIL: printLine("FAIL: " + result.location + " - " + Std.string(result.failure), 1);
case IGNORE:
var ingoredString = result.location;
if(result.description != null) ingoredString += " - " + result.description;
@@ -252,7 +252,7 @@ class ExternalPrintClientJS implements ExternalPrintClient
{
public function new()
{
- #if flash
+ #if (flash && !air)
if(!flash.external.ExternalInterface.available)
{
throw new MUnitException("ExternalInterface not available");
@@ -279,7 +279,7 @@ class ExternalPrintClientJS implements ExternalPrintClient
#end
}
- #if flash
+ #if (flash && !air)
static var externalInterfaceQueue:Array = [];
static var flashInitialised:Bool = false;
static var externalInterfaceCounter:Int = 0;
@@ -400,7 +400,7 @@ class ExternalPrintClientJS implements ExternalPrintClient
public function queue(method:String, ?args:Dynamic):Bool
{
- #if (!js && !flash || nodejs)
+ #if (!js && (!flash || air) || nodejs)
//throw new MUnitException("Cannot call from non JS/Flash targets");
return false;
#end
@@ -411,7 +411,7 @@ class ExternalPrintClientJS implements ExternalPrintClient
var jsCode = convertToJavaScript(method, a);
#if js
return js.Lib.eval(jsCode);
- #elseif flash
+ #elseif (flash && !air)
externalInterfaceQueue.push(jsCode);
#end
return false;
diff --git a/src/massive/munit/client/RichPrintClient.hx b/src/massive/munit/client/RichPrintClient.hx
index 36ce2bc..4e1a7a4 100644
--- a/src/massive/munit/client/RichPrintClient.hx
+++ b/src/massive/munit/client/RichPrintClient.hx
@@ -223,11 +223,23 @@ class RichPrintClient extends PrintClientBase
external.trace(t);
}
+ #if (flash && air)
+ private var flashOutput = "";
+ #end
+
override public function print(value:Dynamic)
{
super.print(value);
#if(neko || cpp || java || cs || python || php || hl || eval)
Sys.print(value);
+ #elseif (flash && air)
+ flashOutput += value;
+ while (flashOutput.indexOf("\n") > -1)
+ {
+ var index = flashOutput.indexOf("\n");
+ flash.Lib.trace(flashOutput.substr(0, index));
+ flashOutput = flashOutput.substr(index + 1);
+ }
#elseif nodejs
js.Node.process.stdout.write(Std.string(value));
#end
diff --git a/test/TestSuite.hx b/test/TestSuite.hx
index 3038c70..6660208 100644
--- a/test/TestSuite.hx
+++ b/test/TestSuite.hx
@@ -5,9 +5,13 @@ import massive.munit.AssertTest;
import massive.munit.async.AsyncDelegateTest;
import massive.munit.async.AsyncFactoryTest;
import massive.munit.async.AsyncTimeoutExceptionTest;
+import massive.munit.async.ChainedAsyncTest;
import massive.munit.async.MissingAsyncDelegateExceptionTest;
+import massive.munit.async.ParallelAsyncTest;
import massive.munit.client.URLRequestTest;
+import massive.munit.DataProviderTest;
import massive.munit.MUnitExceptionTest;
+import massive.munit.PrePostActionAssertsTest;
import massive.munit.TestClassHelperTest;
import massive.munit.TestResultTest;
import massive.munit.TestRunnerTest;
@@ -26,20 +30,24 @@ class TestSuite extends massive.munit.TestSuite
{
super();
- add(massive.munit.AssertionExceptionTest);
- add(massive.munit.AssertTest);
- add(massive.munit.async.AsyncDelegateTest);
- add(massive.munit.async.AsyncFactoryTest);
- add(massive.munit.async.AsyncTimeoutExceptionTest);
- add(massive.munit.async.MissingAsyncDelegateExceptionTest);
- add(massive.munit.client.URLRequestTest);
- add(massive.munit.MUnitExceptionTest);
- add(massive.munit.TestClassHelperTest);
- add(massive.munit.TestResultTest);
- add(massive.munit.TestRunnerTest);
- add(massive.munit.TestSuiteTest);
- add(massive.munit.UnhandledExceptionTest);
- add(massive.munit.util.MathUtilTest);
- add(massive.munit.util.TimerTest);
+ add(massive.munit.AssertionExceptionTest);
+ add(massive.munit.AssertTest);
+ add(massive.munit.async.AsyncDelegateTest);
+ add(massive.munit.async.AsyncFactoryTest);
+ add(massive.munit.async.AsyncTimeoutExceptionTest);
+ add(massive.munit.async.ChainedAsyncTest);
+ add(massive.munit.async.MissingAsyncDelegateExceptionTest);
+ add(massive.munit.async.ParallelAsyncTest);
+ add(massive.munit.client.URLRequestTest);
+ add(massive.munit.DataProviderTest);
+ add(massive.munit.MUnitExceptionTest);
+ add(massive.munit.PrePostActionAssertsTest);
+ add(massive.munit.TestClassHelperTest);
+ add(massive.munit.TestResultTest);
+ add(massive.munit.TestRunnerTest);
+ add(massive.munit.TestSuiteTest);
+ add(massive.munit.UnhandledExceptionTest);
+ add(massive.munit.util.MathUtilTest);
+ add(massive.munit.util.TimerTest);
}
}
diff --git a/test/massive/munit/DataProviderTest.hx b/test/massive/munit/DataProviderTest.hx
new file mode 100644
index 0000000..9eaeb39
--- /dev/null
+++ b/test/massive/munit/DataProviderTest.hx
@@ -0,0 +1,147 @@
+package massive.munit;
+
+#if hamcrest
+import org.hamcrest.Matcher;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+#end
+
+/**
+* Unit tests for Data Provider feature.
+*
+* A data provider is 2D Array of possible values that a test method may be invoked with.
+* - Each row corresponds to a set of values that will be passed for a single invocation of the test method.
+* The values (columns) of a row form the parameters of the test method.
+* - The test method will be invoked as many times as the number of rows are there in that data provider.
+*
+* A data provider could also be a method that returns that Array.
+*/
+class DataProviderTest
+{
+ /** Array data provider */
+ static public var integerValues : Array> = [ [ 2 ], [ 3 ], [ 5 ] ];
+
+ /** static method data provider */
+ static public function booleanValues() : Array>
+ {
+ return [
+ [ true ],
+ [ false ]
+ ];
+ }
+
+ public function new() {
+ }
+
+ /** instance method data provider */
+ public function multipleParameters() : Array>
+ {
+ return [
+ [ [ 1, true ], 4 ],
+ [ [ 2, false ], 16 ],
+ [ [ 3, true ], 64 ]
+ ];
+ }
+
+ // validation data
+ static var actualData01 : Array>;
+ static var actualData02 : Array>;
+ static var actualData03 : Array>;
+
+ @BeforeClass
+ public function beforeClass():Void
+ {
+ actualData01 = new Array>();
+ actualData02 = new Array>();
+ actualData03 = new Array>();
+ }
+
+ @AfterClass
+ public function afterClass():Void
+ {
+ #if hamcrest
+ Assert.isTrue ( new MultiDimensionArrayMatcher( actualData01 ).matches( integerValues ));
+ Assert.isTrue ( new MultiDimensionArrayMatcher( actualData02 ).matches( booleanValues() ));
+ Assert.isTrue ( new MultiDimensionArrayMatcher( actualData03 ).matches( multipleParameters() ));
+ #end
+ }
+
+ @DataProvider("integerValues")
+ @Test
+ public function testArrayDataProvider( num: Int) :Void
+ {
+ actualData01.push( [ num ] ) ;
+ }
+
+ @DataProvider("booleanValues")
+ @Test
+ public function testStaticMethodDataProvider( flag: Bool) :Void
+ {
+ actualData02.push( [ flag ] ) ;
+ }
+
+ @DataProvider("multipleParameters")
+ @Test
+ public function testInstanceMethodDataProvider( argArray: Array, powerOfFour: Int) :Void
+ {
+ actualData03.push( [ argArray, powerOfFour ] ) ;
+ }
+}
+
+
+// --- NOTE: below class should probably go into hamcrest repo
+#if hamcrest
+
+/**
+ * A Hamcrest Matcher to match a multi-dimensional array values, in any order.
+ */
+class MultiDimensionArrayMatcher extends BaseMatcher
+{
+ private var lhsArray: Array;
+
+ public function new (data: Array)
+ {
+ super();
+ lhsArray = (data != null) ? data : [] ;
+ }
+
+ override public function matches (data: Dynamic): Bool
+ {
+ var rhsArray: Array = cast data;
+
+ if (( null == rhsArray ) || ( lhsArray.length != rhsArray.length ))
+ {
+ return false;
+ }
+
+ var matcher: MultiDimensionArrayMatcher>;
+
+ var isAMatch: Bool = true;
+
+ for ( i in 0...rhsArray.length )
+ {
+ if ( Std.is( rhsArray[i], Array ))
+ {
+ matcher = new MultiDimensionArrayMatcher( lhsArray[i] );
+ isAMatch = isAMatch && matcher.matches( rhsArray[i] );
+ }
+ else
+ {
+ isAMatch = isAMatch && ( lhsArray[i] == rhsArray[i] ) ;
+ }
+
+ if (! isAMatch)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ override public function describeTo (description: Description): Void
+ {
+ description.appendText("an n-dimensional array that has same values, in any order ").appendValue( lhsArray) ;
+ }
+}
+#end
diff --git a/test/massive/munit/DataProviderUsageExample.hx b/test/massive/munit/DataProviderUsageExample.hx
new file mode 100644
index 0000000..12ce670
--- /dev/null
+++ b/test/massive/munit/DataProviderUsageExample.hx
@@ -0,0 +1,92 @@
+package massive.munit;
+
+/**
+* Example usage of Data Provider feature.
+*/
+class DataProviderUsageExample
+{
+ public function new( )
+ {}
+
+ // diverse multiple parameter values data provider
+ static public function diverseMultiParamDataProvider() : Array>
+ {
+ return [
+ // has 2 params: one is an Array, another is Bool
+ [
+ // 1st param : Array
+ [ new MockData(),
+ 1.5,
+ { x : 1234 },
+ function() : String
+ {
+ return "1";
+ }
+ // Note: missing optional entry
+ ],
+ // 2nd param : Bool
+ true
+ ],
+
+ [
+ [ new MockData(),
+ 3.0,
+ { x : 1234 },
+ function() : String
+ {
+ return "2";
+ },
+ true
+ ],
+ true
+ ],
+
+ [
+ [ new MockData(),
+ 4.5,
+ { x : 1234 },
+ function() : String
+ {
+ return "3";
+ },
+ true
+ ],
+ false
+ ]
+ ];
+ }
+
+ // example that uses above diverse multiple param/value (static) data provider
+ @DataProvider("diverseMultiParamDataProvider")
+ public function useComplexMultiParamDataProvider( argArray: Array, flag: Bool ):Void
+ {
+ var mock : MockData = cast argArray[0];
+ Assert.areEqual ( "[ MockData instance ]", mock.toString() );
+
+ var weight : Float = cast argArray[1];
+
+ var values : Dynamic = argArray[2];
+ Assert.areEqual ( 1234, values.x );
+
+ var funcHandle : Dynamic = argArray[3];
+ Assert.areEqual ( Std.parseInt(Reflect.callMethod(null, funcHandle, [])) * 1.5, weight );
+
+ var optionalFlag : Bool = false; // default value
+ if (weight > 1.5)
+ {
+ optionalFlag = cast argArray[4];
+ Assert.areEqual ( true, optionalFlag );
+ }
+ }
+}
+
+class MockData
+{
+ public function new()
+ {}
+
+ public function toString():String
+ {
+ return "[ MockData instance ]";
+ }
+}
diff --git a/test/massive/munit/PrePostActionAssertsTest.hx b/test/massive/munit/PrePostActionAssertsTest.hx
new file mode 100644
index 0000000..3082691
--- /dev/null
+++ b/test/massive/munit/PrePostActionAssertsTest.hx
@@ -0,0 +1,63 @@
+package massive.munit;
+
+import massive.munit.async.AsyncFactory;
+import massive.munit.util.Timer;
+
+/**
+ * All unit tests in this class result in a failure state but, that should not halt the TestSuite.
+ * That validates TestRunner capability to not stall when these pre/post-conditional fails.
+ *
+ *
+ * Test cases that fail (by design) produce a result of failure & emit a message containing, "Ignore: this is EXPECTED".
+ * You may consider them as acceptable test results & ignore the exception with that message.
+ *
+ **/
+class PrePostActionAssertsTest
+{
+ private var h1 : Void->Void;
+
+ private var runner:TestRunner;
+ private var client:TestResultClientStub;
+
+ public function new()
+ {}
+
+ @BeforeClass
+ public function beforeClass():Void
+ {}
+
+ @AfterClass
+ public function afterClass():Void
+ {}
+
+ @Before
+ public function setup():Void
+ {}
+
+ @After
+ public function tearDown():Void
+ {
+ Assert.fail("Ignore: this is EXPECTED");
+ }
+
+ // these test methods are not testing any function because
+ // our interest is to validate pre-/post-conditional methods
+ // i.e. the actual tests in this class don't do anything
+ @Test
+ public function testSimple():Void
+ {
+ Assert.areEqual(true,true);
+ }
+
+ @AsyncTest
+ public function testAsync(factory:AsyncFactory) : Void
+ {
+ h1 = factory.createHandler ( this, responseHandlerFunc );
+ Timer.delay ( h1, 100 );
+ }
+
+ public function responseHandlerFunc() : Void
+ {
+ Assert.areEqual(true,true);
+ }
+}
diff --git a/test/massive/munit/TestClassHelperTest.hx b/test/massive/munit/TestClassHelperTest.hx
index 16d3cd3..d543a1f 100644
--- a/test/massive/munit/TestClassHelperTest.hx
+++ b/test/massive/munit/TestClassHelperTest.hx
@@ -39,10 +39,19 @@ class TestClassHelperTest
Assert.isNotNull(helper.test);
Assert.isType(helper.test, TestClassStub);
Assert.areEqual(helper.type, TestClassStub);
- Assert.areEqual(helper.test.beforeClass, helper.beforeClass);
- Assert.areEqual(helper.test.afterClass, helper.afterClass);
- Assert.areEqual(helper.test.before, helper.before);
- Assert.areEqual(helper.test.after, helper.after);
+
+ Assert.areEqual(helper.beforeClass.length, 2);
+ Assert.areEqual(helper.afterClass.length, 2);
+ Assert.areEqual(helper.before.length, 2);
+ Assert.areEqual(helper.after.length, 2);
+ Assert.areEqual(helper.test.beforeClassSuper, helper.beforeClass[0]);
+ Assert.areEqual(helper.test.afterClassSuper, helper.afterClass[1]);
+ Assert.areEqual(helper.test.beforeSuper, helper.before[0]);
+ Assert.areEqual(helper.test.afterSuper, helper.after[1]);
+ Assert.areEqual(helper.test.beforeClass, helper.beforeClass[1]);
+ Assert.areEqual(helper.test.afterClass, helper.afterClass[0]);
+ Assert.areEqual(helper.test.before, helper.before[1]);
+ Assert.areEqual(helper.test.after, helper.after[0]);
}
@Test
diff --git a/test/massive/munit/TestClassStub.hx b/test/massive/munit/TestClassStub.hx
index 9472c87..a2353e2 100644
--- a/test/massive/munit/TestClassStub.hx
+++ b/test/massive/munit/TestClassStub.hx
@@ -32,9 +32,13 @@ import massive.munit.util.Timer;
/**
* @author Mike Stead
*/
-class TestClassStub
+
+class TestClassStub extends TestClassStubSuper
{
- public function new() {}
+ public function new()
+ {
+ super();
+ }
@BeforeClass
public function beforeClass():Void
diff --git a/test/massive/munit/TestClassStubSuper.hx b/test/massive/munit/TestClassStubSuper.hx
new file mode 100644
index 0000000..2d1373e
--- /dev/null
+++ b/test/massive/munit/TestClassStubSuper.hx
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2013 Massive Interactive. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY MASSIVE INTERACTIVE ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MASSIVE INTERACTIVE OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of Massive Interactive.
+ */
+
+package massive.munit;
+
+class TestClassStubSuper
+{
+ public function new()
+ {}
+
+ @BeforeClass
+ public function beforeClassSuper():Void
+ {
+ }
+
+ @AfterClass
+ public function afterClassSuper():Void
+ {
+ }
+
+ @Before
+ public function beforeSuper():Void
+ {
+ }
+
+ @After
+ public function afterSuper():Void
+ {
+ }
+}
diff --git a/test/massive/munit/async/AsyncDelegateTest.hx b/test/massive/munit/async/AsyncDelegateTest.hx
index fd7690c..c8d6984 100644
--- a/test/massive/munit/async/AsyncDelegateTest.hx
+++ b/test/massive/munit/async/AsyncDelegateTest.hx
@@ -93,7 +93,7 @@ class AsyncDelegateTest implements IAsyncDelegateObserver
{
delegate = new AsyncDelegate(this, asyncTestHandler, 10);
delegate.observer = this;
- handler = factory.createHandler(this, onTestTimeout);//created after delegate to ensure delegate timer executes beofre handler one (interval bug in flash when under heavy load)
+ handler = factory.createHandler(this, onTestTimeout);//created after delegate to ensure delegate timer executes before handler one (interval bug in flash when under heavy load)
}
public function asyncTimeoutHandler(delegate:AsyncDelegate):Void
diff --git a/test/massive/munit/async/ChainedAsyncTest.hx b/test/massive/munit/async/ChainedAsyncTest.hx
new file mode 100644
index 0000000..78c1cdd
--- /dev/null
+++ b/test/massive/munit/async/ChainedAsyncTest.hx
@@ -0,0 +1,211 @@
+package massive.munit.async;
+
+import massive.munit.Assert;
+import massive.munit.util.Timer;
+import massive.munit.async.AsyncTimeoutException;
+
+/**
+ *
+ * A chained async delegate has its primary async delegate's response handler (or time-out handler) create another
+ * async delegate (& so on, for any number of levels as needed by the feature being tested).
+ *
+ *
+ *
+ * Test Pass Criteria
+ * Every test case starts with an 'actualValue' of '1', & a pre-set 'expectedValue' (value varies by test-case).
+ * In every response/time-out handler, 'actualValue' is multiplied with a prime factor to mark the method invoked.
+ * If test completes successfully, as designed, 'actualValue' should match 'expectedValue'... checked in @After method.
+ *
+ *
+ *
+ * Test cases that fail (by design) produce a result of failure & emit a message containing, "Ignore: this is EXPECTED".
+ * You may consider them as acceptable test results & ignore the exception with that message.
+ *
+ */
+class ChainedAsyncTest
+{
+ static inline var L1_RESPONS_FACTOR = 11;
+ static inline var L2_RESPONS_FACTOR = 13;
+ static inline var L1_TIMEOUT_FACTOR = 17;
+ static inline var L2_TIMEOUT_FACTOR = 19;
+ static inline var FAILURE_TIMEOUT_FACTOR = 97;
+
+ private var actualValue : Int;
+ private var expectedValue : Int;
+
+ private var h1 : Void->Void;
+ private var h2 : Void->Void;
+ private var h3 : Void->Void;
+ private var h4 : Void->Void;
+ private var h5 : Void->Void;
+ private var t1 : Void->Void;
+ private var t2 : Void->Void;
+ private var t3 : Void->Void;
+ private var t4 : Void->Void;
+ private var t5 : Void->Void;
+
+ public function new()
+ {
+ }
+
+ @Before
+ public function setup():Void
+ {
+ actualValue = 1;
+ }
+
+ @After
+ public function tearDown():Void
+ {
+ Assert.areEqual (expectedValue, actualValue);
+ }
+
+ /**
+ * This test creates an async delegate that is expected to trigger the response handler (responseHandler11).
+ * That handler creates another async delegate, triggerring another response handler (responseHandler12).
+ *
+ * Chaining is occurring in response handlers.
+ *
+ * This test should always Succeed.
+ */
+ @AsyncTest
+ public function testResponseChainSuccess(factory:AsyncFactory) : Void
+ {
+ expectedValue = L1_RESPONS_FACTOR * L2_RESPONS_FACTOR;
+
+ h1 = factory.createHandler ( this, responseHandler11, 250, forbiddenTimeOutHandler );
+ Timer.delay ( h1, 10 );
+ }
+
+ public function responseHandler11() : Void
+ {
+ actualValue *= L1_RESPONS_FACTOR;
+
+ t1 = Async.handler ( this, responseHandler12, 250, forbiddenTimeOutHandler );
+ Timer.delay ( t1, 10 );
+ }
+
+ public function responseHandler12() : Void
+ {
+ actualValue *= L2_RESPONS_FACTOR;
+ }
+
+ /**
+ * This test creates an async delegate that is expected to trigger the response handler (responseHandler21).
+ * That (responseHandler21) creates another async delegate implemented to result in a time-out.
+ * That time-out handler triggers an Assert.fail condition.
+ *
+ * Chaining is occurring in response handlers.
+ *
+ * This test is expected to be reported as FAILed with "Ignore: this is EXPECTED" as part of its error message.
+ */
+ @AsyncTest
+ public function testTimeoutHandlerFailure(factory:AsyncFactory) : Void
+ {
+ expectedValue = L1_RESPONS_FACTOR * FAILURE_TIMEOUT_FACTOR;
+
+ h2 = factory.createHandler ( this, responseHandler21, 250, forbiddenTimeOutHandler );
+ Timer.delay ( h2, 10 );
+ }
+
+ public function responseHandler21() : Void
+ {
+ actualValue *= L1_RESPONS_FACTOR;
+
+ t2 = Async.handler ( this, forbiddenResponseHandler, 250, failingTimeOutHandler );
+ }
+
+ /**
+ * This test creates an async delegate that is expected to trigger the response handler (responseHandler31).
+ * That (responseHandler31) creates another async delegate implemented to result in a time-out.
+ * That time-out handler should not fail.
+ *
+ * Chaining is occurring in response handlers.
+ *
+ * This test should always Succeed.
+ */
+ @AsyncTest
+ public function testTimeoutHandlerSuccess(factory:AsyncFactory) : Void
+ {
+ expectedValue = L1_RESPONS_FACTOR * L2_TIMEOUT_FACTOR;
+
+ h3 = factory.createHandler ( this, responseHandler31, 250, forbiddenTimeOutHandler );
+ Timer.delay ( h3, 10 );
+ }
+
+ public function responseHandler31() : Void
+ {
+ actualValue *= L1_RESPONS_FACTOR;
+
+ t3 = Async.handler ( this, forbiddenResponseHandler, 250, basicTimeOutHandler );
+ }
+
+ /**
+ * This test creates an async delegate that times-out resulting in invoking a time-out handler,
+ * chainABasicTimeOutHandler, that creates another async delegate resulting in a time-out also.
+ * This 2nd time-out handler hould not fail.
+ *
+ * Chaining is occurring in time-out handlers.
+ *
+ * This test should always Succeed.
+ */
+ @AsyncTest
+ public function testChainedTimeoutHandlerSuccess(factory:AsyncFactory) : Void
+ {
+ expectedValue = L1_TIMEOUT_FACTOR * L2_TIMEOUT_FACTOR;
+
+ h4 = factory.createHandler ( this, forbiddenResponseHandler, 250, chainABasicTimeOutHandler );
+ }
+
+ public function chainABasicTimeOutHandler() : Void
+ {
+ actualValue *= L1_TIMEOUT_FACTOR;
+
+ t4 = Async.handler ( this, forbiddenResponseHandler, 250, basicTimeOutHandler );
+ }
+
+ /**
+ * This test creates an async delegate that times-out resulting in invoking a time-out handler,
+ * chainAFailureTimeOutHandler, that creates another async delegate resulting in a time-out also.
+ * This 2nd time-out handler triggers an Assert.fail condition.
+ *
+ * Chaining is occurring in time-out handlers.
+ *
+ * This test is expected to be reported as FAILed with "Ignore: this is EXPECTED" as part of its error message.
+ */
+ @AsyncTest
+ public function testChainedTimeoutHandlerFailure(factory:AsyncFactory) : Void
+ {
+ expectedValue = L1_TIMEOUT_FACTOR * FAILURE_TIMEOUT_FACTOR;
+
+ h5 = factory.createHandler ( this, forbiddenResponseHandler, 250, chainAFailureTimeOutHandler );
+ }
+
+ public function chainAFailureTimeOutHandler() : Void
+ {
+ actualValue *= L1_TIMEOUT_FACTOR;
+
+ t5 = Async.handler ( this, forbiddenResponseHandler, 250, failingTimeOutHandler );
+ }
+
+ public function basicTimeOutHandler() : Void
+ {
+ actualValue *= L2_TIMEOUT_FACTOR;
+ }
+
+ public function failingTimeOutHandler() : Void
+ {
+ actualValue *= FAILURE_TIMEOUT_FACTOR;
+ Assert.fail( "Ignore: this is EXPECTED");
+ }
+
+ public function forbiddenResponseHandler() : Void
+ {
+ actualValue = -555;
+ }
+
+ public function forbiddenTimeOutHandler() : Void
+ {
+ actualValue = -999;
+ }
+}
diff --git a/test/massive/munit/async/ParallelAsyncTest.hx b/test/massive/munit/async/ParallelAsyncTest.hx
new file mode 100644
index 0000000..402b220
--- /dev/null
+++ b/test/massive/munit/async/ParallelAsyncTest.hx
@@ -0,0 +1,149 @@
+package massive.munit.async;
+
+import massive.munit.Assert;
+import massive.munit.util.Timer;
+import massive.munit.async.AsyncTimeoutException;
+
+/**
+ *
+ * Multiple async delegates being created same time & co-existing and executing simultaneously.
+ *
+ *
+ *
+ * Test Pass Criteria
+ * Every test case starts with an 'actualValue' of '1', & a pre-set 'expectedValue' (value varies by test-case).
+ * In every response/time-out handler, 'actualValue' is multiplied with a prime factor to mark the method invoked.
+ * If test completes successfully, as designed, 'actualValue' should match 'expectedValue'... checked in @After method.
+ *
+ *
+ *
+ * Test cases that fail (by design) produce a result of failure & emit a message containing, "Ignore: this is EXPECTED".
+ * You may consider them as acceptable test results & ignore the exception with that message.
+ *
+ */
+class ParallelAsyncTest
+{
+ static inline var SUCCESS_RESPONS_FACTOR = 23;
+ static inline var SUCCESS_TIMEOUT_FACTOR = 29;
+ static inline var FAILURE_TIMEOUT_FACTOR = 97;
+
+ private var actualValue : Int;
+ private var expectedValue : Int;
+
+ private var h1 : Void->Void;
+ private var h2 : Void->Void;
+ private var h3 : Void->Void;
+ private var h4 : Void->Void;
+ private var h5 : Void->Void;
+ private var h6 : Void->Void;
+ private var h7 : Void->Void;
+ private var t1 : Void->Void;
+ private var t2 : Void->Void;
+
+ public function new()
+ {
+ }
+
+ @Before
+ public function setup():Void
+ {
+ actualValue = 1;
+ }
+
+ @After
+ public function tearDown():Void
+ {
+ Assert.areEqual (expectedValue, actualValue);
+ }
+
+ /**
+ * This test creates 3 async delegates, all of which are expected to execute their response handlers & not time-out.
+ *
+ * This test should always Succeed.
+ */
+ @AsyncTest
+ public function testResponseSuccess(factory:AsyncFactory) : Void
+ {
+ expectedValue = SUCCESS_RESPONS_FACTOR * SUCCESS_RESPONS_FACTOR * SUCCESS_RESPONS_FACTOR;
+
+ h1 = factory.createHandler ( this, responseHandler, 250, forbiddenTimeOutHandler );
+ Timer.delay ( h1, 10 );
+
+ h2 = factory.createHandler ( this, responseHandler, 250, forbiddenTimeOutHandler );
+ Timer.delay ( h2, 10 );
+
+ h3 = factory.createHandler ( this, responseHandler, 250, forbiddenTimeOutHandler );
+ Timer.delay ( h3, 10 );
+ }
+
+ /**
+ * This test creates 3 async delegates withe one of them (first) implemented to time-out without error/fail.
+ * It is expected that the other 2 async delegates are not cancelled & execute successfully.
+ *
+ * This test should always Succeed.
+ */
+ @AsyncTest
+ public function testTimeoutHandlerSuccess(factory:AsyncFactory) : Void
+ {
+ expectedValue = SUCCESS_TIMEOUT_FACTOR * SUCCESS_RESPONS_FACTOR * SUCCESS_RESPONS_FACTOR;
+
+ t1 = factory.createHandler ( this, forbiddenResponseHandler, 10, basicTimeOutHandler );
+
+ // as the above time-out handler is NOT a failure handler,
+ // these should CONTINUE to execute!
+
+ h4 = factory.createHandler ( this, responseHandler, 3000, forbiddenTimeOutHandler );
+ Timer.delay ( h4, 1000 );
+
+ h5 = factory.createHandler ( this, responseHandler, 3000, forbiddenTimeOutHandler );
+ Timer.delay ( h5, 1000 );
+ }
+
+ /**
+ * This test creates 3 async delegates withe one of them (first) implemented to time-out WITH error/failure.
+ * It is expected that the other 2 async delegates are Cancelled & do NOT execute.
+ *
+ * This test is expected to be reported as FAILed with "Ignore: this is EXPECTED" as part of its error message.
+ */
+ @AsyncTest
+ public function testTimeoutHandlerFailure(factory:AsyncFactory) : Void
+ {
+ expectedValue = FAILURE_TIMEOUT_FACTOR;
+
+ t2 = factory.createHandler ( this, forbiddenResponseHandler, 10, failingTimeOutHandler );
+
+ // as the above time-out handler IS A failure handler, these should be CANCELLED (if not already triggered) !
+
+ h6 = factory.createHandler ( this, forbiddenResponseHandler, 3000, forbiddenTimeOutHandler );
+ Timer.delay ( h6, 1000 );
+
+ h7 = factory.createHandler ( this, forbiddenResponseHandler, 3000, forbiddenTimeOutHandler );
+ Timer.delay ( h7, 1000 );
+ }
+
+ public function responseHandler() : Void
+ {
+ actualValue *= SUCCESS_RESPONS_FACTOR;
+ }
+
+ public function basicTimeOutHandler() : Void
+ {
+ actualValue *= SUCCESS_TIMEOUT_FACTOR;
+ }
+
+ public function failingTimeOutHandler() : Void
+ {
+ actualValue *= FAILURE_TIMEOUT_FACTOR;
+ Assert.fail( "Ignore: this is EXPECTED");
+ }
+
+ public function forbiddenResponseHandler() : Void
+ {
+ actualValue = -555;
+ }
+
+ public function forbiddenTimeOutHandler() : Void
+ {
+ actualValue = -999;
+ }
+}
diff --git a/tool/template/test-main.mtt b/tool/template/test-main.mtt
index cbc2b8f..40dd86f 100644
--- a/tool/template/test-main.mtt
+++ b/tool/template/test-main.mtt
@@ -61,7 +61,9 @@ class TestMain
{
try
{
- #if flash
+ #if (flash && air)
+ flash.desktop.NativeApplication.nativeApplication.exit();
+ #elseif flash
flash.external.ExternalInterface.call("testResult", successful);
#elseif js
js.Lib.eval("testResult(" + successful + ");");
From 39e328a2e1beb04da58b1caf3ce4d3d1c4f43675 Mon Sep 17 00:00:00 2001
From: Joshua Granick
Date: Fri, 5 Apr 2019 09:20:48 -0700
Subject: [PATCH 2/2] Cleanup
---
src/massive/munit/TestResult.hx | 24 +++++--------------
src/massive/munit/TestRunner.hx | 41 +++++++++++++--------------------
2 files changed, 22 insertions(+), 43 deletions(-)
diff --git a/src/massive/munit/TestResult.hx b/src/massive/munit/TestResult.hx
index 5a285ec..da9afb6 100644
--- a/src/massive/munit/TestResult.hx
+++ b/src/massive/munit/TestResult.hx
@@ -59,8 +59,8 @@ class TestResult
public var className:String = "";
/**
- * An optional description.
- */
+ * An optional description.
+ */
public var description:String = "";
/**
@@ -78,9 +78,9 @@ class TestResult
public var async:Bool = false;
/**
- * Whether the test is ignored or not.
- */
- public var ignore:Bool = false;
+ * Whether the test is ignored or not.
+ */
+ public var ignore:Bool = false;
/**
* Arguments for the test, or null if no args
@@ -102,19 +102,7 @@ class TestResult
/**
* Class constructor.
*/
- public function new()
- {
- passed = false;
- executionTime = 0.0;
- name = "";
- className = "";
- description = "";
- async = false;
- ignore = false;
- args = null;
- error = null;
- failure = null;
- }
+ public function new() {}
function get_type():TestResultType
{
diff --git a/src/massive/munit/TestRunner.hx b/src/massive/munit/TestRunner.hx
index a2bcadb..ecfeebc 100644
--- a/src/massive/munit/TestRunner.hx
+++ b/src/massive/munit/TestRunner.hx
@@ -48,12 +48,6 @@ import java.vm.Thread;
#end
using Lambda;
-#if haxe3
-import haxe.CallStack;
-#else
-import haxe.Stack;
-private typedef CallStack = Stack;
-#end
/**
* Runner used to execute one or more suites of unit tests.
@@ -195,17 +189,11 @@ class TestRunner implements IAsyncDelegateObserver
suiteIndex = 0;
clientCompleteCount = 0;
Assert.assertionCount = 0; // don't really like this static but can't see way around it atm. ms 17/12/10
- emptyParams = new Array();
asyncDelegates = new Array();
- testSuites = new Array();
+ testSuites = [for(suiteType in testSuiteClasses) Type.createInstance(suiteType, emptyParams)];
startTime = Timer.stamp();
- for (suiteType in testSuiteClasses)
- {
- testSuites.push(Type.createInstance(suiteType, new Array()));
- }
-
- #if (!lime && !nme && (neko||cpp))
+ #if (!lime && !nme && (neko || cpp || java))
var self = this;
var runThread:Thread = Thread.create(function()
{
@@ -225,7 +213,7 @@ class TestRunner implements IAsyncDelegateObserver
#end
}
- private function callHelperMethod ( method:Dynamic ):Void
+ private function callHelperMethod(method:Dynamic):Void
{
try
{
@@ -239,13 +227,13 @@ class TestRunner implements IAsyncDelegateObserver
}
catch (e:Dynamic)
{
- var testcaseData: Dynamic = activeHelper.current(); // fetch the test context
- exceptionHandler ( e, testcaseData.result );
+ var testcaseData:Dynamic = activeHelper.current(); // fetch the test context
+ exceptionHandler(e, testcaseData.result);
}
}
- private inline function exceptionHandler ( e:Dynamic, result:TestResult ):Void
+ private inline function exceptionHandler(e:Dynamic, result:TestResult):Void
{
#if hamcrest
if (Std.is(e, org.hamcrest.AssertionException))
@@ -289,7 +277,7 @@ class TestRunner implements IAsyncDelegateObserver
activeHelper.beforeClass.iter(callHelperMethod);
}
executeTestCases();
- if ( ! isAsyncPending() )
+ if (!isAsyncPending())
{
activeHelper.afterClass.iter(callHelperMethod);
}
@@ -303,7 +291,7 @@ class TestRunner implements IAsyncDelegateObserver
testSuites[i] = null;
}
- if ( ! isAsyncPending() )
+ if (!isAsyncPending())
{
var time:Float = Timer.stamp() - startTime;
for (client in clients)
@@ -343,7 +331,8 @@ class TestRunner implements IAsyncDelegateObserver
testStartTime = Timer.stamp();
executeTestCase(testCaseData, testCaseData.result.async);
- if ( ! isAsyncPending() ) {
+ if (!isAsyncPending())
+ {
activeRunner = null; // for SYNC tests: resetting this here instead of clientCompletionHandler
activeHelper.after.iter(callHelperMethod);
}
@@ -382,7 +371,7 @@ class TestRunner implements IAsyncDelegateObserver
Reflect.callMethod(testCaseData.scope, testCaseData.test, result.args);
}
- if (! isAsyncPending())
+ if (!isAsyncPending())
{
result.passed = true;
result.executionTime = Timer.stamp() - testStartTime;
@@ -394,7 +383,7 @@ class TestRunner implements IAsyncDelegateObserver
catch(e:Dynamic)
{
cancelAllPendingAsyncTests();
- exceptionHandler ( e, result );
+ exceptionHandler(e, result);
}
}
@@ -420,7 +409,8 @@ class TestRunner implements IAsyncDelegateObserver
asyncDelegates.remove(delegate);
executeTestCase(testCaseData, false);
- if ( ! isAsyncPending() ) {
+ if (!isAsyncPending())
+ {
activeRunner = null; // for ASync regular cases: resetting this here instead of clientCompletionHandler
activeHelper.after.iter(callHelperMethod);
execute();
@@ -455,7 +445,8 @@ class TestRunner implements IAsyncDelegateObserver
errorCount++;
for (c in clients) c.addError(result);
}
- if ( ! isAsyncPending() ) {
+ if (!isAsyncPending())
+ {
activeRunner = null; // for ASync Time-out cases: resetting this here instead of clientCompletionHandler
activeHelper.after.iter(callHelperMethod);
execute();