diff --git a/test/BaseCase.hx b/test/BaseCase.hx new file mode 100644 index 0000000..b3d0fb4 --- /dev/null +++ b/test/BaseCase.hx @@ -0,0 +1,23 @@ +package; + +import utest.Assert; + +@:keepSub +@:keep +class BaseCase { + var dummy:String = ''; + + public function new() {} + + public function setup() { + dummy = ''; + } + + function assert(expected:Array, generator:Iterable) { + dummy = ''; + for(it in generator) { + Assert.equals(expected.shift(), it); + } + Assert.equals(0, expected.length); + } +} \ No newline at end of file diff --git a/test/async/TestAsyncArray.hx b/test/async/TestAsyncArray.hx new file mode 100644 index 0000000..f5de9ef --- /dev/null +++ b/test/async/TestAsyncArray.hx @@ -0,0 +1,27 @@ +package async; + +import utest.Assert; +import hxasync.*; + +class TestAsyncArray extends BaseCase implements IAsyncable { + + @:async + public function testAwaitInArrayDeclaration() { + var a:Array = [1, @:await Task.forResult(2)]; + Assert.same([1, 2], a); + } + + @:async + public function testAwaitInArrayDeclaration_behindAnotherAwait() { + var a:Array = @:await Task.forResult([1, @:await Task.forResult(2)]); + Assert.same([1, 2], a); + } + + @:async + public function testAwaitInArrayAccess() { + var a:Array = [1, 2]; + var item = a[@:await Task.forResult(1)]; + Assert.same(2, item); + } +} + diff --git a/test/async/TestAsyncAwait.hx b/test/async/TestAsyncAwait.hx new file mode 100644 index 0000000..1003b3c --- /dev/null +++ b/test/async/TestAsyncAwait.hx @@ -0,0 +1,256 @@ +package async; + +import utest.Assert; +import hxasync.*; + +class TestAsyncAwait extends BaseCase implements IAsyncable { + + @:async + public function testAwaitReturnTypeResult() { + var strResult = @:await Task.forResult("string"); + var intResult:Int; + intResult = @:await Task.forResult(123); + var awaitResult = new AwaitResult(); + var objResult:AwaitResult = @:await Task.forResult(awaitResult); + + Assert.is(strResult, String); + Assert.equals("string", strResult); + + Assert.is(intResult, Int); + Assert.equals(123, intResult); + + Assert.is(objResult, AwaitResult); + Assert.equals(awaitResult, objResult); + } + + @:async + public function testAssignAwaitInBlock() { + var i = 1; + i = { + i += 1; + @:await Task.forResult(i + 1); + } + Assert.equals(3, i); + + var i = 1; + i = @:mergeBlock { + i += 1; + @:await Task.forResult(i + 1); + } + Assert.equals(3, i); + + var i = { + var i = 1; + @:await Task.forResult(i + 1); + } + Assert.equals(2, i); + + var i = @:mergeBlock { + var i = 1; + @:await Task.forResult(i + 1); + } + Assert.equals(2, i); + } + + @:async + public function testAwaitReturnResult() { + var result = null; + + result = @:await getAwaitWithResult(); + Assert.notNull(result); + Assert.is(result, AwaitResult); + result = null; + + Assert.isNull(result); + + result = @:await getResult(); + Assert.notNull(result); + Assert.is(result, AwaitResult); + } + + @:async + public function testAwaitResultOperations() { + var float:Float = 0.1; + var int:Int = 5; + + float = @:await Task.forResult(5); + Assert.equals(5, float); + + float += @:await Task.forResult(2); + Assert.notEquals(5, float); + + float -= @:await Task.forResult(2); + Assert.equals(5, float); + + float /= @:await Task.forResult(5); + Assert.equals(1, float); + + float *= @:await Task.forResult(5); + Assert.equals(5, float); + + float %= @:await Task.forResult(1); + Assert.equals(0, float); + + int <<= @:await Task.forResult(2); + Assert.equals(20, int); + + int >>= @:await Task.forResult(1); + Assert.equals(10, int); + + int >>>= @:await Task.forResult(2); + Assert.equals(2, int); + + int |= @:await Task.forResult(6); + Assert.equals(6, int); + + int &= @:await Task.forResult(3); + Assert.equals(2, int); + + int ^= @:await Task.forResult(3); + Assert.equals(1, int); + } + + @:async + public function testAwaitBinOp() { + var int = 2 + @:await Task.forResult(1) + 3; + Assert.equals(6, int); + + var int = @:await Task.forResult(2) * @:await Task.forResult(4); + Assert.equals(8, int); + + var int = @:await getBinopResult(1, 3); + Assert.equals(4, int); + + var bool = false || @:await Task.forResult(true); + Assert.isTrue(bool); + + var bool = true && @:await Task.forResult(false); + Assert.isFalse(bool); + + var bool = 123 > @:await Task.forResult(999); + Assert.isFalse(bool); + + var bool = 123 < @:await Task.forResult(999); + Assert.isTrue(bool); + + var bool = 123 == @:await Task.forResult(999); + Assert.isFalse(bool); + + var bool = 123 != @:await Task.forResult(999); + Assert.isTrue(bool); + } + + @:async + public function testBoolNot() { + var bool = !@:await Task.forResult(true); + Assert.isFalse(bool); + + var bool = !@:await Task.forResult(true) || !@:await Task.forResult(false); + Assert.isTrue(bool); + } + + @:async + public function testAwaitArgument() { + function fn(a:Int, b:String) return a + b.length; + + var result = fn(@:await Task.forResult(1), @:await Task.forResult('123')); + + Assert.equals(4, result); + } + + @:async + public function testAwaitWithAwait() { + function fn(a:Int, b:String) return Task.forResult(a + b.length); + var result = @:await fn(@:await Task.forResult(1), @:await Task.forResult('123')); + Assert.equals(4, result); + + var result = @:await @:await Task.forResult(Task.forResult(2)); + Assert.equals(2, result); + } + + @:async + public function testAwaitInNew() { + var test = new AwaitResult(@:await Task.forResult(10)); + Assert.equals(10, test.value); + } + + @:async + public function testAwaitInField_read() { + var fn = function(i:Int) return [i]; + Assert.equals(1, fn(@:await Task.forResult(10)).length); + } + + @:async + public function testAwaitInField_call() { + var fn = function(i:Int) return [i]; + Assert.equals(10, fn(@:await Task.forResult(10)).pop()); + } + + @:async + public function testAwaitLinearFlow() { + var flag1 = "await1"; + var flag2 = "await2"; + var flag3 = "await3"; + var flowFlags = [flag1]; + + @:await getResult(); + Assert.contains(flag1, flowFlags); + Assert.notContains(flag2, flowFlags); + Assert.notContains(flag3, flowFlags); + flowFlags.push(flag2); + + @:await getAwaitWithResult(); + Assert.contains(flag1, flowFlags); + Assert.contains(flag2, flowFlags); + Assert.notContains(flag3, flowFlags); + flowFlags.push(flag3); + + @:await getAwaitWithResult(); + Assert.contains(flag1, flowFlags); + Assert.contains(flag2, flowFlags); + Assert.contains(flag3, flowFlags); + } + + @:async + public function testAsyncGenericMethod() { + var result = @:await genericMethod(10); + Assert.equals(10, result); + } + + @:async + function genericMethod(value:T):Task { + var result:T = value; + return @:await Task.forResult(result); + } + + @:async + function getAwaitWithResult():Task { + return @:await Task.forResult(new AwaitResult()); + } + + @:async + function getResult():Task { + return new AwaitResult(); + } + + @:async + function getBinopResult(a:Int, b:Int):Task { + return a + @:await Task.forResult(b); + } + + @:async + public function testAsyncLocalVarHasSameNameAsArg() { + Assert.equals(1, @:await localVarWithSameNameAsArg('1')); + } + + @:async + function localVarWithSameNameAsArg(arg:String):Task { + var arg:Int = @:await Task.forResult(Std.parseInt(arg)); + return arg; + } +} + +class AwaitResult { + public var value:Int = 0; + public function new(arg:Int = 0) value = arg; +} diff --git a/test/async/TestAsyncCast.hx b/test/async/TestAsyncCast.hx new file mode 100644 index 0000000..3711576 --- /dev/null +++ b/test/async/TestAsyncCast.hx @@ -0,0 +1,19 @@ +package async; + +import utest.Assert; +import hxasync.*; + +class TestAsyncCast extends BaseCase implements IAsyncable { + + @:async + public function testUntypedCast() { + var result:Int = cast @:await Task.forResult(10); + Assert.equals(10, result); + } + + @:async + public function testTypedCast() { + var result = cast(@:await Task.forResult(this), TestAsyncCast); + Assert.equals(this, result); + } +} \ No newline at end of file diff --git a/test/async/TestAsyncClosure.hx b/test/async/TestAsyncClosure.hx new file mode 100644 index 0000000..099cb09 --- /dev/null +++ b/test/async/TestAsyncClosure.hx @@ -0,0 +1,222 @@ +package async; + +import utest.Assert; + +class TestAsyncClosure extends BaseCase implements IAsyncable { + + @:async + public function testLoopVarNameReuseInClosure() { + for(a in [0, 1, 2]) {} + + var fn = []; + for(a in [0, 1, 2]) { + fn.push(function() { + var a1 = 0; + return a + a1; + }); + } + + Assert.equals(0, fn[0]()); + } + + @:async + public function testNestedClosures() { + var normalClosures = []; + var asyncClosures = []; + for(i in 0...3) { + normalClosures.push(function() { + return function() { + return i; + }; + }); + asyncClosures.push(@:async function() { + return @:async function() { + return i; + }; + }); + } + + Assert.equals(0, normalClosures[0]()()); + Assert.equals(0, @:await (@:await asyncClosures[0]())()); + } + + @:async + public function testAsyncMethodWithNormalClosure() { + var a = 10; + function fn1(arg) return arg + a; + var fn2 = function(arg) return arg + a; + Assert.equals(22, fn1(1) + fn2(1)); + } + + @:async + public function testAsyncClosure() { + var fn = @:async function():Task return @:await Task.forResult(10); + Assert.equals(10, @:await fn()); + + var fn = @:async function():Task { + return @:await Task.forResult(10); + } + Assert.equals(10, @:await fn()); + } + + @:async + public function testAsyncClosure_withoutReturnType() { + var fn = @:async function() return @:await Task.forResult(10); + Assert.equals(10, @:await fn()); + + var fn = @:async function() { return @:await Task.forResult(10); } + Assert.equals(10, @:await fn()); + } + + @:async + public function testAsyncClosure_inNormalClosure() { + var a = 10; + var fn = function() { + return @:async function():Task { + return @:await Task.forResult(a); + } + } + Assert.equals(10, @:await fn()()); + } + + @:async + public function testNormalClosureScope_shouldNotLeakIntoAsyncContext() { + var result = 0; + var assert = Assert.createAsync(function() Assert.equals(2, result)); + + var fn:Int->Void = null; + var recursionLevel = 0; + var arg = 10; + fn = function(arg) { + var z = arg; + if(recursionLevel == 0) { + ++recursionLevel; + fn(arg); + } + haxe.Timer.delay(function() result = ++z, 10); + } + fn(1); + + @:await Task.delay(100); + Assert.equals(10, arg); + assert(); + } + + @:async + public function testLocalVarScopeInFor_overIntRange() { + var normalClosures = []; + var asyncClosures = []; + var asyncClosures = []; + var assert = Assert.createAsync(@:async function() { + Assert.equals(100, normalClosures[0]()); + Assert.equals(100, @:await asyncClosures[0]()); + }); + + for(i in 0...2) { + var a = i; + var b = -1; + @:await Task.delay(1); + normalClosures.push(function() { return a + b; }); + asyncClosures.push(@:async function() return a + b); + @:await Task.delay(1); + b = 100; + } + + assert(); + } + + @:async + public function testLocalVarScopeInFor_overIterator() { + var normalClosures = []; + var asyncClosures = []; + var assert = Assert.createAsync(@:async function() { + Assert.equals(100, normalClosures[0]()); + Assert.equals(100, @:await asyncClosures[0]()); + }); + + var it = 0...2; + for(i in it) { + var a = i; + var b = -1; + @:await Task.delay(1); + normalClosures.push(function() return a + b); + asyncClosures.push(@:async function() return a + b); + @:await Task.delay(1); + b = 100; + } + + assert(); + } + + @:async + public function testLocalVarScopeInWhile() { + var normalClosures = []; + var asyncClosures = []; + var assert = Assert.createAsync(@:async function() { + Assert.equals(100, normalClosures[0]()); + Assert.equals(100, @:await asyncClosures[0]()); + }); + + var i = 0; + while(i < 2) { + var a = i; + var b = -1; + @:await Task.delay(1); + normalClosures.push(function() return a + b); + asyncClosures.push(@:async function() return a + b); + @:await Task.delay(1); + b = 100; + ++i; + } + + assert(); + } + + @:async + public function testLocalVarScopeInDoWhile() { + var normalClosures = []; + var asyncClosures = []; + var assert = Assert.createAsync(@:async function() { + Assert.equals(100, normalClosures[0]()); + Assert.equals(100, @:await asyncClosures[0]()); + }); + + var i = 0; + do { + var a = i; + var b = -1; + @:await Task.delay(1); + normalClosures.push(function() return a + b); + asyncClosures.push(@:async function() return a + b); + @:await Task.delay(1); + b = 100; + ++i; + } while(i < 2); + + assert(); + } + + @:async + public function testLocalVarScopeInNestedLoop() { + var normalClosures = []; + var asyncClosures = []; + var assert = Assert.createAsync(@:async function() { + Assert.equals(100, normalClosures[0]()); + Assert.equals(100, @:await asyncClosures[0]()); + }); + + for(i in 0...2) { + var a = i; + @:await Task.delay(1); + for(j in 0...2) { + var b = -1; + normalClosures.push(function() return a + b); + asyncClosures.push(@:async function() return a + b); + @:await Task.delay(1); + b = 100; + } + } + + assert(); + } +} \ No newline at end of file diff --git a/test/async/TestAsyncFor.hx b/test/async/TestAsyncFor.hx new file mode 100644 index 0000000..2b8c255 --- /dev/null +++ b/test/async/TestAsyncFor.hx @@ -0,0 +1,131 @@ +package async; + +import hxasync.Task; +import utest.Assert; + +class TestAsyncFor extends BaseCase implements IAsyncable { + + @:async + public function testForWithIntervals() { + var counter = 0; + + for (i in 1...10) { + @:await Task.forResult(null); + Assert.equals(i, ++counter); + } + Assert.equals(10, ++counter); + } + + @:async + public function testBreakInForWithIntervals() { + var counter = 0; + + for (i in 1...10) { + @:await Task.forResult(null); + counter++; + break; + } + Assert.equals(1, counter); + } + + @:async + public function testContinueInForWithIntervals() { + var counter = 0; + var continueCounter = 0; + + for (i in 1...10) { + @:await Task.forResult(null); + continueCounter++; + continue; + counter++; + } + Assert.equals(10, ++continueCounter); + Assert.equals(0, counter); + } + + @:async + public function testForReturnResultWithIntervals() { + var result = 5; + + result = @:await getResultFromForWithIntervals(result); + Assert.equals(15, result); + + result = @:await getResultFromForWithIntervals(result); + Assert.equals(55, result); + } + + @:async + public function testForWithIterable() { + var iter = [1, 2, 3, 4, 5, 6, 7, 8, 9]; + var counter = 0; + + for (item in iter) { + @:await Task.forResult(null); + Assert.equals(item, ++counter); + } + Assert.equals(iter.length, counter); + } + + @:async + public function testBreakInForWithIterable() { + var iter = [1, 2, 3, 4, 5, 6, 7, 8, 9]; + var counter = 0; + + for (item in iter) { + @:await Task.forResult(null); + counter++; + break; + } + Assert.equals(1, counter); + } + + @:async + public function testContinueInForWithIterable() { + var iter = [1, 2, 3, 4, 5, 6, 7, 8, 9]; + var counter = 0; + var continueCounter = 0; + + for (item in iter) { + @:await Task.forResult(null); + continueCounter++; + continue; + counter++; + } + Assert.equals(iter.length, continueCounter); + Assert.equals(0, counter); + } + + @:async + public function testForReturnResultWithIterable() { + var result = 5; + + result = @:await getResultFromForWithIterable(result); + Assert.equals(15, result); + + result = @:await getResultFromForWithIterable(result); + Assert.equals(55, result); + } + + @:async + private function getResultFromForWithIntervals(element:Int):Task { + var counter = 0; + var list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + for (i in 0...list.length) { + counter += @:await Task.forResult(list[i]); + if (list[i] == element) return counter; + } + return counter; + } + + @:async + private function getResultFromForWithIterable(element:Int):Task { + var counter = 0; + var list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + for (item in list) { + counter += @:await Task.forResult(item); + if (item == element) return counter; + } + return counter; + } + +} diff --git a/test/async/TestAsyncIf.hx b/test/async/TestAsyncIf.hx new file mode 100644 index 0000000..fca66c9 --- /dev/null +++ b/test/async/TestAsyncIf.hx @@ -0,0 +1,126 @@ +package async; + +import hxasync.Task; +import utest.Assert; + +class TestAsyncIf extends BaseCase implements IAsyncable { + + @:async + public function testIfElse() { + var cond = false; + var result = 0; + + if (cond) { + result += @:await Task.forResult(1); + } + Assert.equals(0, result); + + if (cond || result == 0) { + result += @:await Task.forResult(1); + result *= 5; + } else { + result += @:await Task.forResult(10); + } + Assert.equals(5, result); + } + + @:async + public function testIfElseReturnResult() { + var result = 0; + + result = @:await getResultFromIf(true); + Assert.equals(7, result); + + result += @:await getResultFromIf(false); + Assert.equals(20, result); + } + + @:async + public function testIfWithElseIf() { + var rand = Math.floor(Math.random() * 10); + var result = 0; + + if (rand < 5) { + result = @:await Task.forResult(rand); + } else if (rand == 5) { + result = 5; + } else if (rand > 5 && rand < 8) { + result = rand; + } else { + rand = @:await Task.forResult(0); + } + Assert.equals(rand, result); + } + + @:async + public function testIfWithElseIfReturnResult() { + var result = 0; + + result = @:await getResultFromIfElseIf(result); + Assert.equals(5, result); + + result = @:await getResultFromIfElseIf(result); + Assert.equals(7, result); + + result = @:await getResultFromIfElseIf(result); + Assert.equals(13, result); + + result = @:await getResultFromIfElseIf(result); + Assert.equals(0, result); + } + + @:async + public function testAwaitCondition() { + var result = @:await getResultAwaitCondition(true); + Assert.isTrue(result); + + var result = @:await getResultAwaitCondition(false); + Assert.isFalse(result); + } + + @:async public function testTernary() { + var result = @:await Task.forResult(true) ? @:await Task.forResult(10) : 0; + Assert.equals(10, result); + + var result = @:await Task.forResult(false) ? 0 : @:await Task.forResult(10); + Assert.equals(10, result); + } + +// private section + + @:async + private function getResultFromIf(cond:Bool):Task { + @:await Task.forResult(null); + if (cond) { + return @:await Task.forResult(7); + } else { + return 13; + } + } + + @:async + private function getResultFromIfElseIf(value:Int):Task { + if (value < 5) { + return @:await Task.forResult(5); + } else if (value == 5) { + var outcome = @:await Task.forResult(value); + outcome *= @:await Task.forResult(2); + outcome -= 3; + return outcome; + } else if (value > 5 && value < 8) { + return 13; + } else { + return @:await Task.forResult(0); + } + } + + @:async + private function getResultAwaitCondition(cond:Bool):Task { + if (@:await Task.forResult(cond)) { + return true; + } else { + return false; + } + } + +} diff --git a/test/async/TestAsyncParentheses.hx b/test/async/TestAsyncParentheses.hx new file mode 100644 index 0000000..4c99100 --- /dev/null +++ b/test/async/TestAsyncParentheses.hx @@ -0,0 +1,19 @@ +package async; + +import hxasync.Task; +import utest.Assert; + +class TestAsyncParentheses extends BaseCase implements IAsyncable { + + @:async + public function testParentheses_inBinaryOperations() { + var result = 1 - (2 - @:await Task.forResult(3)) + 1; + Assert.equals(3, result); + } + + @:async + public function testParentheses_inUnaryOperations() { + var result = !(@:await Task.forResult(false)); + Assert.isTrue(result); + } +} diff --git a/test/async/TestAsyncSuper.hx b/test/async/TestAsyncSuper.hx new file mode 100644 index 0000000..87e381a --- /dev/null +++ b/test/async/TestAsyncSuper.hx @@ -0,0 +1,50 @@ +package async; + +import utest.Assert; + +class TestAsyncSuper extends BaseCase implements IAsyncable { + @:async + public function testSuperMethodVoid() { + var test = new ChildClass(); + test.methodVoid(); + Assert.pass(); + } + + @:async + public function testSuperMethod() { + var test = new ChildClass(); + var result = @:await test.method(); + Assert.equals(30, result); + } +} + + +private class ParentClass implements IAsyncable { + public function new() {} + + public function methodVoid() {} + + @:async + public function method():Task { + return @:await Task.forResult(10); + } + + @:async + public function genericMethod(v:T):Task { + return @:await Task.forResult(v); + } +} + +private class ChildClass extends ParentClass { + @:async + override public function methodVoid() { + super.method(); + @:await Task.forResult(10); + } + + @:async + override public function method():Task { + var result = @:await super.method(); + return result + 20; + } +} \ No newline at end of file diff --git a/test/async/TestAsyncSwitch.hx b/test/async/TestAsyncSwitch.hx new file mode 100644 index 0000000..c146445 --- /dev/null +++ b/test/async/TestAsyncSwitch.hx @@ -0,0 +1,120 @@ +package async; + +import hxasync.Task; +import Math; +import utest.Assert; + +class TestAsyncSwitch extends BaseCase implements IAsyncable { + + @:async + public function testSwitchWithoutDefault() { + var result = 0; + var random = Math.round(Math.random() * 9 + 1); + + switch (random) { + case _: + result = @:await Task.forResult(random); + } + Assert.equals(random, result); + } + + @:async + public function testSwitchWithDefault() { + var result = 0; + var random = Math.round(Math.max((Math.random() * 9 + 1), 5)); + + switch (random) { + case 1: + result = @:await Task.forResult(100); + case 2, 3, 4: + // do nothing + default: + result = @:await Task.forResult(random); + } + Assert.equals(random, result); + } + + @:async + public function testSwitchReturnResult() { + var result = 0; + + result = @:await getResultFromSwitch(result); + Assert.equals(6, result); + + result = @:await getResultFromSwitch(result); + Assert.equals(4, result); + + result = @:await getResultFromSwitch(result); + Assert.equals(8, result); + + result = @:await getResultFromSwitch(result); + Assert.equals(8, result); + } + + @:async + public function testSwitchReturnResultWithGuards() { + var result = 0; + + result = @:await getResultFromSwitchWithGuards(result, false); + Assert.equals(0, result); + + result = @:await getResultFromSwitchWithGuards(result); + Assert.equals(6, result); + + result = @:await getResultFromSwitchWithGuards(result); + Assert.equals(4, result); + + result = @:await getResultFromSwitchWithGuards(result); + Assert.equals(8, result); + + result = @:await getResultFromSwitchWithGuards(result); + Assert.equals(0, result); + } + + @:async public function testAwaitSwitchCondition() { + switch(@:await Task.forResult(true)) { + case true: Assert.pass(); + case false: Assert.fail(); + } + + switch(@:await Task.forResult(false)) { + case true: Assert.fail(); + case false: Assert.pass(); + } + } + +// private section + + @:async + private function getResultFromSwitch(subject:Int):Task { + switch (subject) { + case 0, 1, 2, 3: + return 6; + case 4: + return 8; + case 5, 6: + return @:await Task.forResult(4); + case _: + return @:await Task.forResult(subject); + } + } + + @:async + private function getResultFromSwitchWithGuards(subject:Int, ?cond:Bool = true):Task { + switch (subject) { + case 0, 1, 2, 3 if (cond): + return 6; + case 4 if (cond): + return 8; + case 4 if (!cond): + return 7; + case 5, 6: + return @:await Task.forResult(4); + case _ if (subject < 9 && subject > 7): + return 0; + case _: + return @:await Task.forResult(subject); + } + } + +} diff --git a/test/async/TestAsyncTryCatch.hx b/test/async/TestAsyncTryCatch.hx new file mode 100644 index 0000000..fa2f8a7 --- /dev/null +++ b/test/async/TestAsyncTryCatch.hx @@ -0,0 +1,244 @@ +package async; + +import hxasync.Task; +import utest.Assert; + +class TestAsyncTryCatch extends BaseCase implements IAsyncable { + + @:async + public function testHandleNonAsyncExceptions() { + var done = Assert.createAsync(1000); + + try { + methodWithException(); + @:await Task.forResult(null); + } catch(e:Dynamic) { + Assert.raises(function() { throw e; }, String); + Assert.equals("non async exception", e); + } + + try { + throw 123; + @:await Task.forResult(null); + } catch(e:Dynamic) { + Assert.equals(123, e); + } + + done(); + } + + @:async + public function testHandleNonAsyncExceptionsType() { + var done = Assert.createAsync(1000); + + try { + throw 123; + @:await Task.forResult(null); + } catch(e:String) { + Assert.fail("Exception should be Int type."); + } catch(e:Int) { + Assert.equals(123, e); + } catch(e:Dynamic) { + Assert.fail("Exception should be Int type."); + } + + try { + methodWithException(); + @:await Task.forResult(null); + } catch(e:String) { + Assert.equals("non async exception", e); + } catch(e:Int) { + Assert.fail("Exception should be String type."); + } catch(e:Dynamic) { + Assert.fail("Exception should be String type."); + } + + done(); + } + + @:async + public function testNestedHandleNonAsync() { + var done = Assert.createAsync(1000); + + try { + methodWithException(); + @:await Task.forResult(null); + } catch(e:Dynamic) { + Assert.raises(function() { throw e; }, String); + Assert.equals("non async exception", e); + try { + throw "non async exception"; + @:await Task.forResult(null); + @:await Task.forError("async exception"); + @:await Task.forResult(null); + } catch(_e:Dynamic) { + Assert.raises(function() { throw _e; }, String); + Assert.equals("non async exception", e); + } + } + + try { + throw 123; + @:await Task.forResult(null); + } catch(e:Dynamic) { + Assert.equals(123, e); + try { + methodWithException(); + } catch(_e:Dynamic) { + Assert.raises(function() { throw _e; }, String); + Assert.equals("non async exception", _e); + } + } + + done(); + } + + @:async + public function testHandleAsyncExceptions() { + var done = Assert.createAsync(1000); + + try { + @:await Task.forResult(null); + throw "async string exception"; + } catch(e:Dynamic) { + Assert.raises(function() { throw e; }, String); + Assert.equals("async string exception", e); + } + + try { + @:await asyncWithHandleException(); + Assert.pass(); + } catch(e:Dynamic) { + Assert.fail("Unhandled exception."); + } + + try { + @:await Task.forResult(null); + methodWithException(); + } catch(e:Dynamic) { + Assert.raises(function() { throw e; }, String); + Assert.equals("non async exception", e); + } + + done(); + } + + @:async + public function testHandleAsyncExceptionsType() { + var done = Assert.createAsync(1000); + + try { + @:await Task.forError("async handle exception"); + throw 123; + } catch(e:String) { + Assert.equals("async handle exception", e); + } catch(e:Dynamic) { + Assert.fail("Exception should be String type."); + } + + try { + @:await asyncWithHandleException(); + @:await Task.forError(123); + } catch(e:String) { + Assert.fail("Exception should be Int type."); + } catch(e:Int) { + Assert.equals(123, e); + } catch(e:Dynamic) { + Assert.fail("Exception should be Int type."); + } + + try { + @:await asyncWithHandleException(); + @:await asyncWithUnhandledException(); + } catch(e:String) { + Assert.equals("async non handle exception", e); + } catch(e:Int) { + Assert.fail("Exception should be String type."); + } catch(e:Dynamic) { + Assert.fail("Exception should be String type."); + } + + done(); + } + + @:async + public function testNestedHandleAsync() { + var done = Assert.createAsync(1000); + + try { + @:await asyncWithUnhandledException(); + } catch(e:Dynamic) { + try { + @:await Task.forError("async handle exception"); + } catch(_e:Dynamic) { + Assert.raises(function() { throw _e; }, String); + Assert.equals("async handle exception", _e); + } + } + + try { + @:await Task.forResult(null); + @:await asyncWithUnhandledException(); + } catch(e:String) { + try { + @:await Task.forError(123); + } catch(_e:Dynamic) { + Assert.equals(123, _e); + } + } catch(e:Dynamic) { + Assert.fail("Exception should be String type."); + } + + done(); + } + + @:async + public function testNestedTry_secondTryDoesNotCatchException() { + var result = 0; + try { + try { + throw 1; + @:await Task.forResult(0); + } catch(e:String) { + } + } catch(e:Dynamic) { + result += e; + } + Assert.equals(1, result); + } + + @:async + function asyncMetaWithUnhandledException() { + @:await asyncWithUnhandledException(); + } + + @:async + function asyncMetaWithUncaughtException() { + try { + @:await asyncWithUnhandledException(); + Assert.fail(); + } + catch(e:TestAsyncTryCatch) { + Assert.fail(); + } + } + + private function methodWithException() { + throw "non async exception"; + } + + @:async + private function asyncWithHandleException():Task { + try { + @:await Task.forError("async handle exception"); + } catch(e:String) { + // handle exception + } + return null; + } + + private function asyncWithUnhandledException():Task { + throw "async non handle exception"; + } + +} diff --git a/test/async/TestAsyncWhile.hx b/test/async/TestAsyncWhile.hx new file mode 100644 index 0000000..bb77a87 --- /dev/null +++ b/test/async/TestAsyncWhile.hx @@ -0,0 +1,166 @@ +package async; + +import hxasync.Task; +import utest.Assert; + +class TestAsyncWhile extends BaseCase implements IAsyncable { + + @:async + public function testNormalWhile() { + var counter = 0; + + while(counter < 10) { + Assert.notEquals(10, counter); + @:await Task.forResult(null); + counter++; + } + Assert.equals(10, counter); + } + + @:async + public function testBreakInNormalWhile() { + var counter = 0; + + while(counter < 10) { + @:await Task.forResult(null); + counter++; + break; + } + Assert.equals(1, counter); + } + + @:async + public function testContinueInNormalWhile() { + var counter = 0; + var continueCounter = 0; + + while(continueCounter < 10) { + @:await Task.forResult(null); + continueCounter++; + continue; + counter++; + } + Assert.equals(10, continueCounter); + Assert.equals(0, counter); + } + + @:async + public function testNormalWhileReturnResult() { + var result = 5; + + result = @:await getResultFromNormalWhile(result); + Assert.equals(15, result); + + result = @:await getResultFromNormalWhile(result); + Assert.equals(45, result); + + result = @:await getResultFromNormalWhile(result, 10); + Assert.equals(0, result); + } + + @:async + public function testNotNormalWhile() { + var counter = 0; + + do { + @:await Task.forResult(null); + counter++; + } while(counter < 10); + Assert.equals(10, counter); + + do { + @:await Task.forResult(null); + counter++; + } while(counter < 10); + Assert.notEquals(10, counter); + } + + @:async + public function testBreakInNotNormalWhile() { + var counter = 0; + + do { + @:await Task.forResult(null); + counter++; + break; + } while(counter < 10); + Assert.equals(1, counter); + } + + @:async + public function testContinueInNotNormalWhile() { + var counter = 0; + var continueCounter = 0; + + do { + @:await Task.forResult(null); + continueCounter++; + continue; + counter++; + } while(continueCounter < 10); + Assert.equals(10, continueCounter); + Assert.equals(0, counter); + } + + @:async + public function testNotNormalWhileReturnResult() { + var result = 5; + + result = @:await getResultFromNotNormalWhile(result); + Assert.equals(15, result); + + result = @:await getResultFromNotNormalWhile(result); + Assert.equals(45, result); + + result = @:await getResultFromNotNormalWhile(result, 10); + Assert.equals(10, result); + } + + @:async + public function testAwaitInCondition() { + var condition = 3; + var result = 0; + while(@:await Task.forResult(--condition > 0)) { + result++; + } + Assert.equals(2, result); + + var condition = 3; + var result = 0; + do { + result++; + } while(@:await Task.forResult(--condition > 0)); + Assert.equals(3, result); + } + +// private section + + @:async + private function getResultFromNormalWhile(element:Int, ?startCount:Int = 0):Task { + var counter = startCount; + var outcome = 0; + while (counter < 10) { + outcome += counter; + if (counter == element) { + return @:await Task.forResult(outcome); + } + counter++; + } + return outcome; + } + + @:async + private function getResultFromNotNormalWhile(element:Int, ?startCount:Int = 0):Task { + var counter = startCount; + var outcome = 0; + do { + outcome += counter; + if (counter == element) { + return @:await Task.forResult(outcome); + } + counter++; + } while(counter < 10); + return outcome; + } + +} diff --git a/test/async/TestEdgeCases.hx b/test/async/TestEdgeCases.hx new file mode 100644 index 0000000..c72d8cf --- /dev/null +++ b/test/async/TestEdgeCases.hx @@ -0,0 +1,68 @@ +package async; + +import utest.Assert; + +class TestEdgeCases extends BaseCase implements IAsyncable { + @:async + public function testTupleCase() { + @:await TupleCase.tupleCase(); + Assert.pass(); + } + + @:async + public function testArrayComprehension() { + var result:Int = [for(i in 0...2) i][0]; + Assert.equals(0, result); + } + + @:async + public function testStringInterpolation() { + var v = 'world'; + Assert.equals('hello, world', 'hello, $v'); + } + + @:async + public function testAsyncGenerciMethodWithConstraints() { + var result = @:await genericMethodWithConstraints(ConstraintTest); + Assert.equals(ConstraintTest, result); + } + + @:async + @:access(hxasync.v2.async.MetaTest) + public function testThirdPartyMeta() { + Assert.isTrue(MetaTest.privateVar); + } + + @:async + function genericMethodWithConstraints(cls:Class):Task> { + return @:await Task.forResult(cls); + } +} + + +private class Tuple2 { + public var item1:T1; + public var item2:T2; + + public function new(item1:T1, item2:T2) { + this.item1 = item1; + this.item2 = item2; + } +} + +private class TupleCase implements IAsyncable { + @:async + public static function tupleCase():Task { + var data:Tuple2 = @:await Task.call(function()return new Tuple2(10, 20)); + Assert.equals(10, data.item1); + Assert.equals(20, data.item2); + } +} + + +private interface IFace1 {} +private interface IFace2 {} +private class ConstraintTest implements IFace1 implements IFace2 {} +private class MetaTest { + static var privateVar = true; +} \ No newline at end of file diff --git a/test/async/TestIAwaitable.hx b/test/async/TestIAwaitable.hx new file mode 100644 index 0000000..63c436d --- /dev/null +++ b/test/async/TestIAwaitable.hx @@ -0,0 +1,47 @@ +package async; + +import hxasync.IAwaitable.AwaitableIdGenerator; +import utest.Assert; +import hxasync.*; + +class TestIAwaitable extends BaseCase implements IAsyncable { + @:async + public function testVarAwait() { + var awaitable = new DummyAwaiter(1); + var result = @:await awaitable; + Assert.equals(1, result); + } + + @:async + public function testAssign() { + var awaitable = new DummyAwaiter(1); + var result; + result = @:await awaitable; + Assert.equals(1, result); + } + + @:async + public function testReturnAwait() { + Assert.equals(1, @:await returnAwait()); + } + + @:async + function returnAwait():Task { + var awaitable = new DummyAwaiter(1); + return @:await awaitable; + } +} + +private class DummyAwaiter implements IAwaitable { + public var id(default,null):Int = 0; + var task:Task; + + public function new(result:Int) { + id = AwaitableIdGenerator.nextId(); + task = Task.forResult(result); + } + + public function getAwaiter():Task { + return task; + } +} \ No newline at end of file diff --git a/test/async/TestTask.hx b/test/async/TestTask.hx new file mode 100644 index 0000000..dd4e901 --- /dev/null +++ b/test/async/TestTask.hx @@ -0,0 +1,626 @@ +package async; + +import hxasync.executors.TimerTaskExecutor; +import utest.Assert; +import hxasync.Task; +import hxasync.TaskCompletionSource; +import hxasync.TaskCanceledException; + +class TestTask extends BaseCase implements IAsyncable { + + public function testPrimitives() { + var complete = Task.forResult(5); + var error = Task.forError("task error"); + var cancelled = Task.cancelled(); + + Assert.isTrue(complete.isCompleted); + Assert.equals(5, complete.result); + Assert.isFalse(complete.isFaulted); + Assert.isFalse(complete.isCancelled); + Assert.isTrue(complete.isSuccessed); + + Assert.isTrue(error.isCompleted); + Assert.is(error.error, String); + Assert.equals("task error", error.error); + Assert.isTrue(error.isFaulted); + Assert.isFalse(error.isCancelled); + Assert.isFalse(error.isSuccessed); + + Assert.isTrue(cancelled.isCompleted); + Assert.isFalse(cancelled.isFaulted); + Assert.isTrue(cancelled.isCancelled); + Assert.isFalse(cancelled.isSuccessed); + } + + public function testSynchronousContinuation() { + var complete = Task.forResult(5); + var error = Task.forError("task error"); + var cancelled = Task.cancelled(); + + var completeHandled = false; + var errorHandled = false; + var cancelledHandled = false; + + complete.continueWith(function(task:Task) { + Assert.same(complete, task); + Assert.isTrue(task.isCompleted); + Assert.equals(5, task.result); + Assert.isFalse(task.isFaulted); + Assert.isFalse(task.isCancelled); + Assert.isTrue(task.isSuccessed); + completeHandled = true; + }); + + error.continueWith(function(task:Task) { + Assert.same(error, task); + Assert.isTrue(task.isCompleted); + Assert.is(task.error, String); + Assert.equals("task error", task.error); + Assert.isTrue(task.isFaulted); + Assert.isFalse(task.isCancelled); + Assert.isFalse(task.isSuccessed); + Assert.isNull(task.result); + errorHandled = true; + }); + + cancelled.continueWith(function(task : Task) { + Assert.same(cancelled, task); + Assert.isTrue(task.isCompleted); + Assert.isFalse(task.isFaulted); + Assert.isTrue(task.isCancelled); + Assert.isFalse(task.isSuccessed); + cancelledHandled = true; + }); + + Assert.isTrue(completeHandled); + Assert.isTrue(errorHandled); + Assert.isTrue(cancelledHandled); + } + + public function testSynchronousChaining() { + var first = Task.forResult(1); + var second = first.continueWith(function(task:Task):Int { + return 2; + }); + var third = second.continueWithTask(function(task:Task):Task { + return Task.forResult(3); + }); + + Assert.isTrue(first.isCompleted); + Assert.isTrue(second.isCompleted); + Assert.isTrue(third.isCompleted); + + Assert.equals(1, first.result); + Assert.equals(2, second.result); + Assert.equals(3, third.result); + } + + public function testSynchronousCancellation() { + var first = Task.forResult(1); + var second = first.continueWith(function(task:Task):Int { + throw new TaskCanceledException(); + }); + + Assert.isTrue(first.isCompleted); + Assert.isTrue(second.isCancelled); + } + + public function testSynchronousTaskCancellation() { + var first = Task.forResult(1); + var second = first.continueWithTask(function(task:Task):Task { + throw new TaskCanceledException(); + }); + + Assert.isTrue(first.isCompleted); + Assert.isTrue(second.isCancelled); + } + + public function testBackgroundCall() { + var timerExecutor = new TimerTaskExecutor(10); + var task:Task = null; + var done = Assert.createAsync(function() { + Assert.equals(5, task.result); + }, 5000); + + Task.call(function():Int { + return 5; + }, timerExecutor).continueWith(function(t:Task) { + task = t; + done(); + }); + } + + public function testBackgroundError() { + var timerExecutor = new TimerTaskExecutor(10); + var task:Task = null; + var done = Assert.createAsync(function() { + Assert.isTrue(task.isFaulted); + Assert.is(task.error, String); + }, 5000); + + Task.call(function():Int { + throw "task error"; + }, timerExecutor).continueWith(function(t:Task) { + task = t; + done(); + }); + } + + public function testBackgroundCancellation() { + var timerExecutor = new TimerTaskExecutor(10); + var task:Task = null; + var done = Assert.createAsync(function() { + Assert.isTrue(task.isCancelled); + }, 5000); + + Task.call(function():Int { + throw new TaskCanceledException(); + }, timerExecutor).continueWith(function(t:Task) { + task = t; + done(); + }); + } + + public function testContinueOnTimerExecutor() { + var timerExecutor = new TimerTaskExecutor(10); + var task:Task = null; + var done = Assert.createAsync(function() { + Assert.equals(3, task.result); + }, 5000); + + Task.call(function():Int { + return 1; + }, timerExecutor).continueWith(function(t:Task):Int { + return t.result + 1; + }, timerExecutor).continueWithTask(function(t:Task):Task { + return Task.forResult(t.result + 1); + }, timerExecutor).continueWith(function(t:Task) { + task = t; + done(); + }); + } + + public function testWhenAllNoTasks() { + var task = Task.whenAll(new Array>()); + + Assert.isTrue(task.isCompleted); + Assert.isFalse(task.isFaulted); + Assert.isFalse(task.isCancelled); + Assert.isTrue(task.isSuccessed); + } + + public function testWhenAnyResultFirstSuccess() { + var task:Task> = null; + var tasks = new Array>(); + var firstToCompleteSuccess = Task.call(function():Int { + return 2000; + }, new TimerTaskExecutor(50)); + var done = Assert.createAsync(function() { + Assert.isTrue(task.isCompleted); + Assert.isFalse(task.isFaulted); + Assert.isFalse(task.isCancelled); + Assert.isTrue(task.isSuccessed); + Assert.same(firstToCompleteSuccess, task.result); + Assert.isTrue(task.result.isCompleted); + Assert.isFalse(task.result.isFaulted); + Assert.isFalse(task.result.isCancelled); + Assert.isTrue(task.result.isSuccessed); + Assert.equals(2000, task.result.result); + }, 5000); + + addTasksWithRandomCompletions(tasks, 5); + tasks.push(firstToCompleteSuccess); + addTasksWithRandomCompletions(tasks, 5); + + Task.whenAny(tasks).continueWith(function(t:Task>) { + task = t; + done(); + }); + } + + public function testWhenAnyFirstSuccess() { + var task:Task> = null; + var tasks = new Array>(); + var firstToCompleteSuccess = Task.call(function():String { + return "SUCCESS"; + }, new TimerTaskExecutor(50)); + var done = Assert.createAsync(function() { + Assert.isTrue(task.isCompleted); + Assert.isFalse(task.isFaulted); + Assert.isFalse(task.isCancelled); + Assert.isTrue(task.isSuccessed); + Assert.same(firstToCompleteSuccess, task.result); + Assert.isTrue(task.result.isCompleted); + Assert.isFalse(task.result.isFaulted); + Assert.isFalse(task.result.isCancelled); + Assert.isTrue(task.result.isSuccessed); + Assert.equals("SUCCESS", task.result.result); + }, 5000); + + addTasksWithRandomCompletions(tasks, 5); + tasks.push(firstToCompleteSuccess); + addTasksWithRandomCompletions(tasks, 5); + + Task.whenAny(tasks).continueWith(function(t:Task>) { + task = t; + done(); + }); + } + + public function testWhenAnyFirstError() { + var task:Task> = null; + var error = "task error"; + var tasks = new Array>(); + var firstToCompleteError = Task.call(function():String { + throw error; + }, new TimerTaskExecutor(50)); + var done = Assert.createAsync(function() { + Assert.isTrue(task.isCompleted); + Assert.isFalse(task.isFaulted); + Assert.isFalse(task.isCancelled); + Assert.isTrue(task.isSuccessed); + Assert.same(firstToCompleteError, task.result); + Assert.isTrue(task.result.isCompleted); + Assert.isTrue(task.result.isFaulted); + Assert.isFalse(task.result.isCancelled); + Assert.isFalse(task.result.isSuccessed); + Assert.same(error, task.result.error); + }, 5000); + + addTasksWithRandomCompletions(tasks, 5); + tasks.push(firstToCompleteError); + addTasksWithRandomCompletions(tasks, 5); + + Task.whenAny(tasks).continueWith(function(t:Task>) { + task = t; + done(); + }); + } + + public function testWhenAnyFirstCancelled() { + var task:Task> = null; + var tasks = new Array>(); + var firstToCompleteError = Task.call(function():String { + throw new TaskCanceledException(); + }, new TimerTaskExecutor(50)); + var done = Assert.createAsync(function() { + Assert.isTrue(task.isCompleted); + Assert.isFalse(task.isFaulted); + Assert.isFalse(task.isCancelled); + Assert.isTrue(task.isSuccessed); + Assert.same(firstToCompleteError, task.result); + Assert.isTrue(task.result.isCompleted); + Assert.isFalse(task.result.isFaulted); + Assert.isTrue(task.result.isCancelled); + Assert.isFalse(task.result.isSuccessed); + }, 5000); + + addTasksWithRandomCompletions(tasks, 5); + tasks.push(firstToCompleteError); + addTasksWithRandomCompletions(tasks, 5); + + Task.whenAny(tasks).continueWith(function(t:Task>) { + task = t; + done(); + }); + } + + public function testWhenAllSuccess() { + var task:Task = null; + var tasks = new Array>(); + var done = Assert.createAsync(function() { + Assert.isTrue(task.isCompleted); + Assert.isFalse(task.isFaulted); + Assert.isFalse(task.isCancelled); + Assert.isTrue(task.isSuccessed); + for (t in tasks) { + Assert.isTrue(t.isCompleted); + } + }, 5000); + + for (i in 0 ... 20) { + tasks.push(Task.call(function() : Unit { + // do nothing + return null; + }, new TimerTaskExecutor(randomInt(10, 50)))); + } + + Task.whenAll(tasks).continueWith(function(t:Task) { + task = t; + done(); + }); + } + + public function testWhenAllOneError() { + var task:Task = null; + var error = "task error"; + var tasks = new Array>(); + var done = Assert.createAsync(function() { + Assert.isTrue(task.isCompleted); + Assert.isTrue(task.isFaulted); + Assert.isFalse(task.isCancelled); + Assert.isFalse(task.isSuccessed); + Assert.is(task.error, Array); + Assert.equals((cast task.error:Array).length, 1); + Assert.same((cast task.error:Array)[0], error); + for (t in tasks) { + Assert.isTrue(t.isCompleted); + } + }, 5000); + + for (i in 0 ... 20) { + tasks.push(Task.call(function() : Unit { + if (i == 10) { + throw error; + } + return null; + }, new TimerTaskExecutor(randomInt(10, 50)))); + } + + Task.whenAll(tasks).continueWith(function(t:Task) { + task = t; + done(); + }); + } + + public function testWhenAllTwoErrors() { + var task:Task = null; + var error0 = "task error_0"; + var error1 = "task error_1"; + var tasks = new Array>(); + var done = Assert.createAsync(function() { + Assert.isTrue(task.isCompleted); + Assert.isTrue(task.isFaulted); + Assert.isFalse(task.isCancelled); + Assert.isFalse(task.isSuccessed); + Assert.is(task.error, Array); + Assert.equals((cast task.error:Array).length, 2); + Assert.same((cast task.error:Array)[0], error0); + Assert.same((cast task.error:Array)[1], error1); + for (t in tasks) { + Assert.isTrue(t.isCompleted); + } + }, 5000); + + for (i in 0 ... 20) { + tasks.push(Task.call(function():Unit { + if (i == 10) { + throw error0; + } else if (i == 11) { + throw error1; + } + return null; + }, new TimerTaskExecutor(10 + i * 10))); + } + + Task.whenAll(tasks).continueWith(function(t:Task) { + task = t; + done(); + }); + } + + public function testWhenAllCancel() { + var task:Task = null; + var tasks = new Array>(); + var done = Assert.createAsync(function() { + Assert.isTrue(task.isCompleted); + Assert.isFalse(task.isFaulted); + Assert.isTrue(task.isCancelled); + Assert.isFalse(task.isSuccessed); + for (t in tasks) { + Assert.isTrue(t.isCompleted); + } + }, 5000); + + for (i in 0 ... 20) { + var tcs = new TaskCompletionSource(); + + Task.call(function() { + if (i == 10) { + tcs.setCancelled(); + } else { + tcs.setResult(null); + } + }, new TimerTaskExecutor(randomInt(10, 50))); + tasks.push(tcs.task); + } + + Task.whenAll(tasks).continueWith(function(t:Task) { + task = t; + done(); + }); + } + + public function testWhenAllResultNoTasks() { + var task = Task.whenAllResult(new Array>()); + + Assert.isTrue(task.isCompleted); + Assert.isFalse(task.isFaulted); + Assert.isFalse(task.isCancelled); + Assert.isTrue(task.isSuccessed); + Assert.is(task.result, Array); + Assert.equals(task.result.length, 0); + } + + public function testWhenAllResultSuccess() { + var task:Task> = null; + var tasks = new Array>(); + var done = Assert.createAsync(function() { + Assert.isTrue(task.isCompleted); + Assert.isFalse(task.isFaulted); + Assert.isFalse(task.isCancelled); + Assert.isTrue(task.isSuccessed); + Assert.equals(tasks.length, task.result.length); + for (i in 0 ... tasks.length) { + var t = tasks[i]; + Assert.isTrue(t.isCompleted); + Assert.equals(t.result, task.result[i]); + } + }, 5000); + + for (i in 0 ... 20) { + tasks.push(Task.call(function():Int { + return (i + 1); + }, new TimerTaskExecutor(randomInt(10, 50)))); + } + + Task.whenAllResult(tasks).continueWith(function(t:Task>) { + task = t; + done(); + }); + } + + public function testAsyncChaining() { + var task:Task = null; + var tasks = new Array>(); + var sequence = new Array(); + var result = Task.forResult(null); + var done = Assert.createAsync(function() { + Assert.equals(20, sequence.length); + for (i in 0 ... 20) { + Assert.equals(i, sequence[i]); + } + }, 5000); + + for (i in 0 ... 20) { + result = result.continueWithTask(function(task:Task):Task { + return Task.call(function():Unit { + sequence.push(i); + return null; + }, new TimerTaskExecutor(randomInt(10, 50))); + }); + } + + result.continueWith(function(t:Task) { + task = t; + done(); + }); + } + + public function testOnSuccess() { + var continuation = function(task:Task):Int { + return task.result + 1; + }; + var complete = Task.forResult(5).onSuccess(continuation); + var error = Task.forError("task error").onSuccess(continuation); + var cancelled = Task.cancelled().onSuccess(continuation); + + Assert.isTrue(complete.isCompleted); + Assert.equals(6, complete.result); + Assert.isFalse(complete.isFaulted); + Assert.isFalse(complete.isCancelled); + Assert.isTrue(complete.isSuccessed); + + Assert.isTrue(error.isCompleted); + Assert.is(error.error, String); + Assert.equals("task error", error.error); + Assert.isTrue(error.isFaulted); + Assert.isFalse(error.isCancelled); + Assert.isFalse(error.isSuccessed); + + Assert.isTrue(cancelled.isCompleted); + Assert.isFalse(cancelled.isFaulted); + Assert.isTrue(cancelled.isCancelled); + Assert.isFalse(cancelled.isSuccessed); + } + + public function testOnSuccessTask() { + var continuation = function(task:Task):Task { + return Task.forResult(task.result + 1); + }; + var complete = Task.forResult(5).onSuccessTask(continuation); + var error = Task.forError("task error").onSuccessTask(continuation); + var cancelled = Task.cancelled().onSuccessTask(continuation); + + Assert.isTrue(complete.isCompleted); + Assert.equals(6, complete.result); + Assert.isFalse(complete.isFaulted); + Assert.isFalse(complete.isCancelled); + Assert.isTrue(complete.isSuccessed); + + Assert.isTrue(error.isCompleted); + Assert.is(error.error, String); + Assert.equals("task error", error.error); + Assert.isTrue(error.isFaulted); + Assert.isFalse(error.isCancelled); + Assert.isFalse(error.isSuccessed); + + Assert.isTrue(cancelled.isCompleted); + Assert.isFalse(cancelled.isFaulted); + Assert.isTrue(cancelled.isCancelled); + Assert.isFalse(cancelled.isSuccessed); + } + + public function testContinueWhile() { + var count = 0; + var handled = false; + + Task.forResult(null).continueWhile(function():Bool { + return (count < 10); + }, function(task:Task):Task { + count++; + return null; + }).continueWith(function(task:Task):Void { + Assert.equals(10, count); + handled = true; + }); + + Assert.isTrue(handled); + } + + public function testContinueWhileAsync() { + var count = 0; + var done = Assert.createAsync(function() { + Assert.equals(10, count); + }, 5000); + + Task.forResult(null).continueWhile(function():Bool { + return (count < 10); + }, function(task:Task):Task { + count++; + return null; + }, new TimerTaskExecutor(10)).continueWith(function(task:Task) { + done(); + }); + } + + public function testNullError() { + var error = Task.forError(null); + + Assert.isTrue(error.isCompleted); + Assert.same(error.error, null); + Assert.isTrue(error.isFaulted); + Assert.isFalse(error.isCancelled); + Assert.isFalse(error.isSuccessed); + } + +// private section + + private function addTasksWithRandomCompletions( + tasks:Array>, + numberOfTasksToLaunch:Int, + minDelay:Int = 100, + maxDelay:Int = 200, + minResult:Int = 0, + maxResult:Int = 1000 + ) { + for (i in 0...numberOfTasksToLaunch) { + tasks.push(Task.call(function():Int { + var rand : Float = Math.random(); + + if (rand >= 0.7) { + throw "task error"; + } else if (rand >= 0.4) { + throw new TaskCanceledException(); + } + + return randomInt(minResult, maxResult); + }, new TimerTaskExecutor(randomInt(minDelay, maxDelay)))); + } + } + + private function randomInt(from:Int, to:Int):Int { + return from + Math.floor((to - from + 1) * Math.random()); + } + +} diff --git a/test/async/TestTaskCancellation.hx b/test/async/TestTaskCancellation.hx new file mode 100644 index 0000000..0135fcb --- /dev/null +++ b/test/async/TestTaskCancellation.hx @@ -0,0 +1,190 @@ +package async; + +import utest.Assert; +import hxasync.cancellation.CancellationToken; +import hxasync.cancellation.CancellationTokenSource; + +class TestTaskCancellation extends BaseCase implements IAsyncable { + + public function testSynchronousCancellation() { + var cts = new CancellationTokenSource(); + var ct = cts.token; + var continuationMarker = 0; + + var tcs = new TaskCompletionSource(); + var task = tcs.task; + + task.continueWith(function(task:Task) : Unit { + Assert.fail("Task should be cancelled!"); + return null; + }, ct).continueWith(function(task:Task):Unit { + Assert.isTrue(task.isCompleted); + Assert.isTrue(task.isCancelled); + Assert.isFalse(task.isFaulted); + return null; + }, CancellationToken.NONE); + + task.continueWith(function(task:Task) { + Assert.equals(100, task.result); + Assert.isTrue(task.isCompleted); + Assert.isFalse(task.isCancelled); + Assert.isFalse(task.isFaulted); + }); + + cts.cancel(); + + tcs.setResult(100); + + Task.forResult(null, ct).continueWith(function(task:Task) { + Assert.fail("Task should be cancelled!"); + }, ct); + + Task.forResult(null, ct).continueWith(function(task:Task) { + Assert.isTrue(task.isCompleted); + Assert.isTrue(task.isCancelled); + Assert.isFalse(task.isFaulted); + continuationMarker = 5; + }); + Assert.equals(5, continuationMarker); + + Task.forResult(null, ct).continueWith(function(task:Task) { + Assert.isTrue(task.isCompleted); + Assert.isTrue(task.isCancelled); + Assert.isFalse(task.isFaulted); + continuationMarker = 7; + }, CancellationToken.NONE); + Assert.equals(7, continuationMarker); + + Task.forResult(null, ct).continueWith(function(task:Task) : Unit { + Assert.fail("Task should be cancelled!"); + return null; + }, ct).onSuccess(function(task:Task) : Unit { + Assert.fail("Task couldn't be successed!"); + return null; + }); + + Task.forResult(null, ct).continueWith(function(task:Task) : Unit { + Assert.isTrue(task.isCompleted); + Assert.isTrue(task.isCancelled); + Assert.isFalse(task.isFaulted); + continuationMarker = 9; + return null; + }).onSuccess(function(task:Task) : Unit { + Assert.equals(9, continuationMarker); + Assert.isTrue(task.isCompleted); + Assert.isFalse(task.isCancelled); + Assert.isFalse(task.isFaulted); + continuationMarker = 11; + return null; + }); + Assert.equals(11, continuationMarker); + } + + @:async + public function testSynchronousCancellationWithAwait() { + var cts = new CancellationTokenSource(); + var ct = cts.token; + var result = 0; + + result = @:await Task.forResult(5, ct); + Assert.equals(5, result); + + cts.cancel(); + try { + result += @:await Task.forResult(5, ct); + Assert.fail("Task should be cancelled!"); + } catch(e:Dynamic) { + Assert.isTrue(Std.is(e, TaskCanceledException)); + return; + } + Assert.fail("Await should be never started!"); + result += @:await Task.forResult(5); + Assert.fail("Await should be never completed!"); + } + + public function testAsynchronousCancellation() { + var cts = new CancellationTokenSource(); + var ct = cts.token; + var continuationMarker = 0; + + var done = Assert.createAsync(function() { + Assert.equals(1, continuationMarker); + }, 5000); + + Task.delay(10, ct).continueWith(function(task:Task) { + Assert.fail("Task should be cancelled!"); + }, ct); + + Task.delay(10, ct).continueWith(function(task:Task) { + Assert.isTrue(task.isCompleted); + Assert.isTrue(task.isCancelled); + Assert.isFalse(task.isFaulted); + continuationMarker++; + done(); + }); + + cts.cancel(); + } + + @:async + public function testAsynchronousCancellationWithAwait() { + var cts = new CancellationTokenSource(); + var ct = cts.token; + var result = 0; + + var done = Assert.createAsync(function() { + Assert.equals(15, result); + }, 5000); + + try { + for (i in 0...10) { + @:await Task.delay(10, ct); + result += i; + if (i == 5) { + cts.cancel(); + } + } + } catch(e:TaskCanceledException) { + Assert.isTrue(Std.is(e, TaskCanceledException)); + done(); + return; + } + Assert.fail("Task should be cancelled!"); + } + + public function testDelayedAsynchronousCancellation() { + var cts1 = new CancellationTokenSource(CancellationType.DELAYED_CANCELLATION(100)); + var cts2 = new CancellationTokenSource(); + var continuationMarker = 0; + + var done = Assert.createAsync(function() { + Assert.equals(2, continuationMarker); + }, 5000); + + Task.delay(5, cts1.token).continueWith(function(task:Task) { + Assert.isTrue(task.isCompleted); + Assert.isFalse(task.isCancelled); + Assert.isFalse(task.isFaulted); + continuationMarker++; + }, cts1.token); + + Task.delay(150, cts1.token).continueWith(function(task:Task) { + Assert.fail("Task should be cancelled!"); + }, cts1.token); + + Task.delay(5, cts2.token).continueWith(function(task:Task) { + Assert.isTrue(task.isCompleted); + Assert.isFalse(task.isCancelled); + Assert.isFalse(task.isFaulted); + continuationMarker++; + }, cts2.token); + + Task.delay(100, cts2.token).continueWith(function(task:Task) { + Assert.fail("Task should be cancelled!"); + }, cts2.token); + + cts2.cancelAfter(CancellationType.DELAYED_CANCELLATION(50)); + + Task.delay(200).onSuccess(function(task:Task) { done(); }); + } +} diff --git a/test/yield/TestYieldBasic.hx b/test/yield/TestYieldBasic.hx new file mode 100644 index 0000000..7cbdde9 --- /dev/null +++ b/test/yield/TestYieldBasic.hx @@ -0,0 +1,122 @@ +package yield; + +import utest.Assert; + +class TestYieldBasic extends BaseCase implements IAsyncable { + + public function testBasicYieldReturn() { + assert([10, 20], basicYieldReturn()); + Assert.equals('123', dummy); + } + + @:yield function basicYieldReturn():Iterable { + dummy += '1'; + @:yield return 10; + dummy += '2'; + @:yield return 20; + dummy += '3'; + } + + + public function testBasicYieldReturn_multipleIterations() { + var generator = basicYieldReturn(); + assert([10, 20], generator); + assert([10, 20], generator); + Assert.equals('123', dummy); + } + + @:yield function basicYieldReturn_multipleIterations():Iterable { + dummy += '1'; + @:yield return 10; + dummy += '2'; + @:yield return 20; + dummy += '3'; + } + + + public function testBasicYieldBreak() { + assert([10], basicYieldBreak()); + Assert.equals('12', dummy); + } + + @:yield function basicYieldBreak() { + dummy += '1'; + @:yield return 10; + dummy += '2'; + @:yield break; + dummy += '3'; + @:yield return 20; + dummy += '4'; + } + + + public function testLocalVars() { + assert([10, 25, 40, 19, 30], localVars(10, 20, 30)); + } + + @:yield function localVars(a:Int, b:Int, a1:Int) { + var q = b; + @:yield return a; + var a = 5; + @:yield return a + q; + var q = q * 2; + @:yield return q; + for(a in 1...2) q = a * 10; + for(c in 1...2) q += 5; + for(i in 0...2) { + for(j in 0...2) q += i + j; + } + @:yield return q; + @:yield return a1; + } + + + public function testLocalVars_sameVarNameInTwoChildScopes() { + assert([10], localVars_sameVarNameInTwoChildScopes(true)); + assert([20], localVars_sameVarNameInTwoChildScopes(false)); + } + + @:yield function localVars_sameVarNameInTwoChildScopes(condition:Bool) { + if(condition) { + var v = 10; + @:yield return v; + } else { + var v = 'ab'; + @:yield return v.length * 10; + } + } + + + public function testLocalFunction() { + assert([10, 20, 30], localFunction()); + } + + @:yield function localFunction() { + inline function local1() return 20; + function local2() { + return 30; + } + @:yield return 10; + @:yield return local1(); + var value = local2(); + @:yield return value; + } + + + public function testInheritance() { + var result = [for(it in descendantsOfParent()) it]; + Assert.equals(2, result.length); + } + + @:yield function descendantsOfParent():Iterable { + @:yield return new Child1(); + @:yield return new Child2(); + } +} + + +private class Parent { + public function new() {} +} +private class Child1 extends Parent {} +private class Child2 extends Parent {} \ No newline at end of file diff --git a/test/yield/TestYieldClosure.hx b/test/yield/TestYieldClosure.hx new file mode 100644 index 0000000..ff4eb2f --- /dev/null +++ b/test/yield/TestYieldClosure.hx @@ -0,0 +1,88 @@ +package yield; + +import utest.Assert; + +class TestYieldClosure extends BaseCase implements IAsyncable { + + // @:yield function closure(arg) { + // var fn = @:yield function(arg2) { + // } + // @:yield function another(arg2) { + // trace({arg2:arg2}); + // } + // } + + var anchor:Dynamic; + + public function testClosure() { + assert([20, 40, 60, 80, 20, 40, 60, 80, 100], closure(2)); + Assert.equals('1234512345', dummy); + } + + @:yield function closure(arg) { + var a:Dynamic = arg; + anchor = a; + var fn = @:yield function(arg2) { + var b:Dynamic = arg; + anchor = b; + dummy += '1'; + @:yield return arg * 10; + dummy += '2'; + @:yield return a * 20; + dummy += '3'; + @:yield return b * 30; + dummy += '4'; + @:yield return arg2 * 40; + dummy += '5'; + } + for(i in fn(a)) { + @:yield return i; + } + @:yield function another(arg2) { + var b:Dynamic = arg; + anchor = b; + dummy += '1'; + @:yield return arg * 10; + dummy += '2'; + @:yield return a * 20; + dummy += '3'; + @:yield return b * 30; + dummy += '4'; + @:yield return arg2 * 40; + dummy += '5'; + for(i in (@:yield function() @:yield return arg2 * 50)()) { + @:yield return i; + } + } + for(i in another(a)) { + @:yield return i; + } + } + + + public function testClosure_nested() { + assert([100], closure_nested(10)); + } + + @:yield function closure_nested(arg) { + @:yield function another(arg2) { + var fn = @:yield function() @:yield return arg2 * 10; + for(i in fn()) @:yield return i; + } + for(i in another(arg)) { + @:yield return i; + } + } + + + public function testClosure_withoutYield() { + assert([0, 10], closure_withoutYield(1)); + } + + @:yield function closure_withoutYield(arg:Int) { + var fn = function() return arg * 10; + for(i in 0...2) { + @:yield return fn() * i; + } + } +} \ No newline at end of file diff --git a/test/yield/TestYieldFor.hx b/test/yield/TestYieldFor.hx new file mode 100644 index 0000000..2f7973e --- /dev/null +++ b/test/yield/TestYieldFor.hx @@ -0,0 +1,87 @@ +package yield; + +import utest.Assert; + +class TestYieldFor extends BaseCase implements IAsyncable { + + public function testFor_basicYieldReturn() { + assert([11, 21, 31], for_basicYieldReturn(1)); + Assert.equals('01122334', dummy); + } + + @:yield function for_basicYieldReturn(arg:Int) { + dummy += '0'; + for(i in 1...4) { + dummy += i; + @:yield return i * 10 + arg; + dummy += i; + } + dummy += '4'; + } + + + public function testFor_basicYieldBreak() { + assert([10], for_basicYieldBreak()); + Assert.equals('012', dummy); + } + + @:yield function for_basicYieldBreak() { + dummy += '0'; + @:yield return 10; + dummy += '1'; + for(i in 2...100) { + dummy += i; + @:yield break; + dummy += i; + } + dummy += '101'; + } + + + public function testFor_nested() { + assert([0, 1, 10, 11], for_nested()); + Assert.equals('0[><><][><><]2', dummy); + } + + @:yield function for_nested() { + dummy += '0'; + for(i in 0...2) { + dummy += '['; + for(j in 0...2) { + dummy += '>'; + @:yield return i * 10 + j; + dummy += '<'; + } + dummy += ']'; + } + dummy += '2'; + } + + + public function testFor_breakContinue() { + assert([0, -1, 2], for_breakContinue()); + Assert.equals('12356789235235670', dummy); + } + + @:yield function for_breakContinue() { + dummy += '1'; + for(i in 0...10) { + dummy += '2'; + while(true) { + dummy += '3'; + break; + dummy += '4'; + } + dummy += '5'; + if(i == 1) continue; + dummy += '6'; + @:yield return i; + dummy += '7'; + if(i == 2) break; + dummy += '8'; + @:yield return -1; + dummy += '9'; + } + dummy += '0'; + } +} \ No newline at end of file diff --git a/test/yield/TestYieldIf.hx b/test/yield/TestYieldIf.hx new file mode 100644 index 0000000..d56fbc5 --- /dev/null +++ b/test/yield/TestYieldIf.hx @@ -0,0 +1,95 @@ +package yield; + +import utest.Assert; + +class TestYieldIf extends BaseCase implements IAsyncable { + + public function testIf_withoutElse() { + assert([10, 20, 30, 40], ifWithoutElse(true)); + Assert.equals('1234567', dummy); + assert([10, 30], ifWithoutElse(false)); + Assert.equals('12567', dummy); + } + + @:yield function ifWithoutElse(condition:Bool) { + dummy += '1'; + @:yield return 10; + dummy += '2'; + if(condition) { + dummy += '3'; + @:yield return 20; + dummy += '4'; + } + dummy += '5'; + @:yield return 30; + dummy += '6'; + if(condition) @:yield return 40; + dummy += '7'; + } + + + public function testIfElse() { + assert([10], ifElse(true)); + Assert.equals('123678', dummy); + assert([20, 30], ifElse(false)); + Assert.equals('14568', dummy); + } + + @:yield function ifElse(condition:Bool) { + dummy += '1'; + if(condition) { + dummy += '2'; + @:yield return 10; + dummy += '3'; + } else { + dummy += '4'; + @:yield return 20; + dummy += '5'; + } + dummy += '6'; + if(condition) { + dummy += '7'; + } else @:yield return 30; + dummy += '8'; + } + + + public function testIfElse_withoutYield_runInSingleState() { + assert([10], ifElseNoYield(true)); + assert([10], ifElseNoYield(false)); + } + + @:yield function ifElseNoYield(condition:Bool) { + var state = __ctx__.state; //__ctx__ is generated by build macros + if(condition) { + Assert.equals(state, __ctx__.state); + } else { + Assert.equals(state, __ctx__.state); + } + Assert.equals(state, __ctx__.state); + + @:yield return 10; + } + + + public function testIfElse_nestedIfs() { + assert([10], nestedIfs(true)); + Assert.equals('123456', dummy); + assert([], nestedIfs(false)); + Assert.equals('16', dummy); + } + + @:yield function nestedIfs(condition:Bool) { + dummy += '1'; + if(condition) { + dummy += '2'; + if(condition) { + dummy += '3'; + @:yield return 10; + dummy += '4'; + } + dummy += '5'; + } + dummy += '6'; + } +} \ No newline at end of file diff --git a/test/yield/TestYieldSwitch.hx b/test/yield/TestYieldSwitch.hx new file mode 100644 index 0000000..0363aaa --- /dev/null +++ b/test/yield/TestYieldSwitch.hx @@ -0,0 +1,102 @@ +package yield; + +import utest.Assert; + +private enum Example { + One; + Two(v:Int); + Three(v:String); + Four; +} + +class TestYieldSwitch extends BaseCase implements IAsyncable { + + public function testSwitch() { + assert([10, 30], basicSwitch(One)); + Assert.equals('1230-', dummy); + assert([20, 30], basicSwitch(Two(20))); + Assert.equals('1450-', dummy); + assert([5, 30], basicSwitch(Three('hello'))); + Assert.equals('1670-', dummy); + assert([30], basicSwitch(Three('h'))); + Assert.equals('1h0-', dummy); + assert([], basicSwitch(Four)); + Assert.equals('18', dummy); + } + + @:yield function basicSwitch(arg) { + dummy += '1'; + switch(arg) { + case One: + dummy += '2'; + @:yield return 10; + dummy += '3'; + case Two(v): + dummy += '4'; + @:yield return v; + dummy += '5'; + case Three(v) if(v.length > 1): + dummy += '6'; + @:yield return v.length; + dummy += '7'; + case Three(v): + dummy += v; + default: + dummy += '8'; + @:yield break; + dummy += '9'; + } + dummy += '0'; + @:yield return 30; + dummy += '-'; + } + + public function testSwitch_withoutYield() { + assert([30], switch_withoutYield(One)); + assert([30], switch_withoutYield(Two(10))); + assert([30], switch_withoutYield(Three('hello'))); + assert([30], switch_withoutYield(Four)); + } + + @:yield function switch_withoutYield(arg) { + var state = __ctx__.state; + switch(arg) { + case One: Assert.equals(state, __ctx__.state); + case Two(v): Assert.equals(state, __ctx__.state); + case Three(v): Assert.equals(state, __ctx__.state); + case _: Assert.equals(state, __ctx__.state); + } + Assert.equals(state, __ctx__.state); + @:yield return 30; + } + + public function testSwitch_multipleSwitch() { + assert([20, 30, 40], switch_multipleSwitch(One)); + assert([10, 20, 40], switch_multipleSwitch(Two(999))); + } + + @:yield function switch_multipleSwitch(arg) { + switch(arg) { + case Two(_): @:yield return 10; + case _: + } + @:yield return 20; + switch(arg) { + case One: @:yield return 30; + case _: + } + @:yield return 40; + } + + public function testNoYieldSwitchAsArgument() { + assert([10], noYieldSwitchAsArgument(10)); + } + + @:yield function noYieldSwitchAsArgument(arg:Int) { + var fn = function(v:Int) return v; + var result = fn(switch(arg) { + case _: arg; + }); + @:yield return result; + } +} \ No newline at end of file diff --git a/test/yield/TestYieldTryCatch.hx b/test/yield/TestYieldTryCatch.hx new file mode 100644 index 0000000..3bb50a2 --- /dev/null +++ b/test/yield/TestYieldTryCatch.hx @@ -0,0 +1,170 @@ +package yield; + +import utest.Assert; + +class TestYieldTryCatch extends BaseCase implements IAsyncable { + + public function testTryCatch_noCatch() { + assert([10], tryCatch_noCatch()); + Assert.equals('1235', dummy); + } + + @:yield function tryCatch_noCatch() { + dummy += '1'; + try { + dummy += '2'; + @:yield return 10; + dummy += '3'; + } + catch(e:Dynamic) { + dummy += '4'; + } + dummy += '5'; + } + + + public function testTryCatch_oneCatch() { + assert([10], tryCatch_oneCatch()); + Assert.equals('12456', dummy); + } + + @:yield function tryCatch_oneCatch() { + dummy += '1'; + try { + dummy += '2'; + throw 'Error!'; + dummy += '3'; + } + catch(e:Dynamic) { + dummy += '4'; + @:yield return 10; + dummy += '5'; + } + dummy += '6'; + } + + + public function testTryCatch_multiCatch() { + assert([10], tryCatch_multiCatch('Error')); + Assert.equals('12458', dummy); + assert([20], tryCatch_multiCatch(123)); + Assert.equals('12678', dummy); + } + + @:yield function tryCatch_multiCatch(throwValue:Dynamic) { + dummy += '1'; + try { + dummy += '2'; + throw throwValue; + dummy += '3'; + } + catch(e:String) { + dummy += '4'; + @:yield return 10; + dummy += '5'; + } + catch(e:Dynamic) { + dummy += '6'; + @:yield return 20; + dummy += '7'; + } + dummy += '8'; + } + + + public function testTryCatch_nested() { + assert([10], tryCatch_nested(1)); + Assert.equals('124569', dummy); + assert([20], tryCatch_nested('Error!')); + Assert.equals('12789', dummy); + } + + @:yield function tryCatch_nested(throwValue:Dynamic) { + dummy += '1'; + try { + try { + dummy += '2'; + throw throwValue; + dummy += '3'; + } + catch(e:Int) { + dummy += '4'; + @:yield return 10; + dummy += '5'; + } + dummy += '6'; + } + catch(e:Dynamic) { + dummy += '7'; + @:yield return 20; + dummy += '8'; + } + dummy += '9'; + } + + + public function testTryCatch_withoutYield_runInSingleState() { + assert([10], tryCatchNoYield(true)); + } + + @:yield function tryCatchNoYield(condition:Bool) { + var state = __ctx__.state; //__ctx__ is generated by build macros + try { + Assert.equals(state, __ctx__.state); + } + catch(e:Dynamic){ + Assert.equals(state, __ctx__.state); + } + Assert.equals(state, __ctx__.state); + + @:yield return 10; + } + + + public function testTryCatch_exceptionNotCaught_thrownOutOfYieldContext() { + try { + assert([], tryCatchNotCaught()); + Assert.fail(); + } + catch(e:String) { + Assert.equals('Error!', e); + Assert.equals('12', dummy); + } + } + + @:yield function tryCatchNotCaught() { + dummy += '1'; + try { + dummy += '2'; + throw "Error!"; + dummy += '3'; + @:yield return 10; + dummy += '4'; + } + catch(e:Int){ + dummy += '5'; + } + dummy += '6'; + } + + + public function testTryCatch_captureVariable() { + assert([10], tryCatch_captureVariable()); + Assert.equals('12456', dummy); + } + + @:yield function tryCatch_captureVariable() { + dummy += '1'; + try { + dummy += '2'; + throw 10; + dummy += '3'; + } + catch(e:Int) { + dummy += '4'; + @:yield return e; + dummy += 5; + } + dummy += '6'; + } +} \ No newline at end of file diff --git a/test/yield/TestYieldWhile.hx b/test/yield/TestYieldWhile.hx new file mode 100644 index 0000000..b9f0247 --- /dev/null +++ b/test/yield/TestYieldWhile.hx @@ -0,0 +1,97 @@ +package yield; + +import utest.Assert; + +class TestYieldWhile extends BaseCase implements IAsyncable { + + public function testWhile_basicYieldReturn() { + assert([11, 21, 31], while_basicYieldReturn(1)); + Assert.equals('01122334', dummy); + } + + @:yield function while_basicYieldReturn(arg:Int) { + dummy += '0'; + var i = 1; + while(i < 4) { + dummy += i; + @:yield return i * 10 + arg; + dummy += i; + i++; + } + dummy += '4'; + } + + + public function testWhile_basicYieldBreak() { + assert([10], while_basicYieldBreak()); + Assert.equals('012', dummy); + } + + @:yield function while_basicYieldBreak() { + dummy += '0'; + @:yield return 10; + dummy += '1'; + var i = 2; + while(i < 100) { + dummy += i; + @:yield break; + dummy += i; + i++; + } + dummy += '101'; + } + + + public function testWhile_nested() { + assert([0, 1, 10, 11], while_nested()); + Assert.equals('0[><><][><><]2', dummy); + } + + @:yield function while_nested() { + dummy += '0'; + var i = 0; + while(i < 2) { + dummy += '['; + var j = 0; + while(j < 2) { + dummy += '>'; + @:yield return i * 10 + j; + dummy += '<'; + j++; + } + dummy += ']'; + i++; + } + dummy += '2'; + } + + + public function testWhile_breakContinue() { + assert([0, -1, 2], while_breakContinue()); + Assert.equals('12356789235235670', dummy); + } + + @:yield function while_breakContinue() { + dummy += '1'; + var i = -1; + while(i < 10) { + i++; + dummy += '2'; + while(true) { + dummy += '3'; + break; + dummy += '4'; + } + dummy += '5'; + if(i == 1) continue; + dummy += '6'; + @:yield return i; + dummy += '7'; + if(i == 2) break; + dummy += '8'; + @:yield return -1; + dummy += '9'; + } + dummy += '0'; + } +} \ No newline at end of file