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();