//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------

WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");

var nextCount = 0;
var returnCount = 0;
var value1 = 1;
var done1 = false;
var iterable = {};
var shouldNextThrow = false;
var shouldReturnThrow = false;
iterable[Symbol.iterator] = function () {
    return {
        next: function() {
            nextCount++;
            if (shouldNextThrow) { throw new Error('Exception from next function'); }
            return {value : value1, done:done1};
        },
        return: function (value) {
            returnCount++;
            if (shouldReturnThrow) { throw new Error('Exception from return function'); }
            return {done:true};
        }
    };
};

var obj = {
  set prop(val) {
    throw new Error('From setter');
  }
};

var tests = [
    {
		name : "Destructuring - no .return function defined is a valid scenario",
		body : function () {
			var iterable = {};
			iterable[Symbol.iterator] = function () {
				return {
					next : function () {
						return {
							value : 10,
							done : false
						};
					}
				};
			};

			var [a] = iterable;
			assert.areEqual(a, 10, "Destructuring declaration calls next on iterator and should get 10");
            var b;
            [b] = iterable;
			assert.areEqual(b, 10, "Destructuring expression calls next on iterator and should get 10");
		}
	},
    {
		name : "Destructuring - validating that the iterable has .return field but not as a function",
		body : function () {
            var returnFnc = undefined;
            var iterable = {};
            iterable[Symbol.iterator] = function() {
              return {
                next: function() {
                    return {value:10, done:false};
                },
                return: returnFnc
              };
            };
            
            var a;
            assert.doesNotThrow(function() {
                [a] = iterable;
            }, "iterator has return field which returns 'undefined' and that should not throw");
            
            assert.doesNotThrow(function() {
                returnFnc = null;
                [a] = iterable;
            }, "iterator has return field which returns 'null' and that should not throw");
            
            assert.throws(function () { returnFnc = {}; [a] = iterable; }, TypeError, 
                    "return field is not a function and returns other than 'undefined'/'null' should throw Type Error");
		}
	},
    {
		name : "Destructuring - basic validation with .return function when the pattern is empty or has one element",
		body : function () {
            nextCount = 0;
            returnCount = 0;
            [] = iterable;
            assert.areEqual(nextCount, 0, "Empty LHS pattern should not call the next function");
            assert.areEqual(returnCount, 1, "Evaluation of empty LHS pattern will call return function");

            nextCount = 0;
            returnCount = 0;
            var [] = iterable;
            assert.areEqual(nextCount, 0, "Empty LHS declaration pattern should not call the next function");
            assert.areEqual(returnCount, 1, "Evaluation of empty LHS declaration pattern call return function");
            
            nextCount = 0;
            returnCount = 0;
            [,] = iterable;
            assert.areEqual(nextCount, 1, "Comma operator causes the next to be called");
            assert.areEqual(returnCount, 1, "Exhausting the LHS pattern (after comma) will call the call return function");

            var a;
            nextCount = 0;
            returnCount = 0;
            [a] = iterable;
            assert.areEqual(nextCount, 1, "Evaluating destructuring element will call next function");
            assert.areEqual(returnCount, 1, "Exhausting the LHS pattern (after comma) will call the call return function");
            
		}
	},
    {
		name : "Destructuring - validation with .return function with nesting pattern",
		body : function () {
            nextCount = 0;
            returnCount = 0;
            [[a]] = [iterable];
            assert.areEqual(nextCount, 1, "Nested array pattern will call next function when 'a' is evauluated");
            assert.areEqual(returnCount, 1, "When nested array pattern exhausts the return function will be called");
            
            nextCount = 0;
            returnCount = 0;
            value1 = iterable;
            [a, [a]] = iterable;
            
            assert.areEqual(nextCount, 3, "Recursive iterators on RHS - next function will be called 3 times in order to exhaust elements");
            assert.areEqual(returnCount, 2, "Recursive iterators on RHS - return function for two nested iterators will be called");
            value1 = 1; // Reset it back

            nextCount = 0;
            returnCount = 0;
            value1 = iterable;
            var [a, [a]] = iterable;
            
            assert.areEqual(nextCount, 3, "Array declaration - Recursive iterators on RHS - next function will be called 3 times in order to exhaust elements");
            assert.areEqual(returnCount, 2, "Array declaration - Recursive iterators on RHS - return function for two nested iterators will be called");
            value1 = 1; // Reset it back
		}
	},    
    {
		name : "Destructuring - change .done to true will not call .return function",
		body : function () {
            nextCount = 0;
            returnCount = 0;
            done1 = true;
            var [a] = iterable;
            
            assert.areEqual(nextCount, 1, "next function is called to evaluate the element on the LHS");
            assert.areEqual(returnCount, 0, "'done' is set true on next function call - which will ensure that return function is not called");
            done1 = false;
		}
	},    
    {
		name : "Destructuring - validating that exception will call the .return function",
		body : function () {
            nextCount = 0;
            returnCount = 0;
            assert.throws(function () { [obj.prop] = iterable; }, Error, "Pattern throws error while assigning .value", "From setter");
            assert.areEqual(returnCount, 1, "Abrupt completion due to exception will call the return function");

            nextCount = 0;
            returnCount = 0;
            value1 = iterable;
            assert.throws(function () { [k, [obj.prop]] = iterable; }, Error, "Nested pattern throws error while assigning .value", "From setter");
            assert.areEqual(returnCount, 2, "Nested recursive iterators - abrupt completion due to exception will call the return function");
            value1 = 1;

            nextCount = 0;
            returnCount = 0;
            value1 = iterable;
            assert.throws(function () { [[[[obj.prop]]]] = iterable; }, Error, "Nested pattern throws error while assigning .value", "From setter");
            assert.areEqual(nextCount, 4, "Nested recursing iterators - 4 times next function called due to 4 level depth of array pattern");
            assert.areEqual(returnCount, 4, "Nested recursing iterators - 4 times return function called due to abrupt comletion");
            value1 = 1;
            
            done1 = true;
            nextCount = 0;
            returnCount = 0;
            assert.throws(function () { [obj.prop] = iterable; }, Error, "Pattern throws error while assigning .value", "From setter");
            assert.areEqual(returnCount, 0, "Ensuring that return function is not called when 'done' is set to true during abrupt completion");
            done1 = false; // Reset it back.
		}
	},    
    {
		name : "Destructuring - chained iterators will have their .return function called correctly",
		body : function () {
            var nextCount1 = 0;
            var nextCount2 = 0;
            var returnCount1 = 0;
            var returnCount2 = 0;
            
            var iterable = {};
            iterable[Symbol.iterator] = function() {
              return {
                next: function() {
                    nextCount1++;
                  return { value: iterable2, done: false };
                },
                return: function() {
                    returnCount1++;
                    return {done:true};
                },
              };
            };

            var iterable2 = {};
            iterable2[Symbol.iterator] = function() {
              return {
                next: function() {
                    nextCount2++;
                  return { value: [0], done: false };
                },
                return: function() {
                    returnCount2++;
                    return {done:true};
                }
              };
            };

             nextCount1 = 0;
             nextCount2 = 0;
             returnCount1 = 0;
             returnCount2 = 0;
             assert.throws(function () {[[obj.prop]] = iterable; }, Error, "Pattern throws error while assigning .value", "From setter");
             assert.areEqual(nextCount1, 1, "next function for the first iterator is called");
             assert.areEqual(nextCount2, 1, "next function for the second iterator is called");
             assert.areEqual(returnCount1, 1, "return function for the second iterator is called" );
             assert.areEqual(returnCount2, 1, "return function for the first iterator is called");
		}
	},    
    {
		name : "Destructuring - .return function should not be called when .next function throws",
		body : function () {
            shouldNextThrow = true;
            returnCount = 0;
            assert.throws(function () { var [a] = iterable; }, Error, "Array declaration - Calling .next throws", "Exception from next function");
            assert.areEqual(returnCount, 0, "Ensuring that return function is not called in array declaration pattern");
             
            assert.throws(function () { [a] = iterable; }, Error, "Array expression - Calling .next throws", "Exception from next function");
            assert.areEqual(returnCount, 0, "Ensuring that return function is not called in array expression pattern");
             
            assert.throws(function () { var [...a] = iterable; }, Error, "Array declaration with rest - Calling .next throws", "Exception from next function");
            assert.areEqual(returnCount, 0, "Ensuring that return function is not called when evalauting rest in array declaration pattern");
             
            assert.throws(function () { [...a] = iterable; }, Error, "Array expression with rest - Calling .next throws", "Exception from next function");
            assert.areEqual(returnCount, 0, "Ensuring that return function is not called when evaluating rest in array expression pattern");
             
            shouldNextThrow = false;
		}
	},    
    {
		name : "Destructuring - .return function should not be called when fetching .value throws",
		body : function () {
            var iterable2 = {};
            iterable2[Symbol.iterator] = function() {
              return {
                next: function() {
                    return {get value () { throw new Error('Exception while getting value'); }, done:false};
                },
                return: function() {
                    assert.fail('return should not be called');
                    return {};
                },
              };
            };

            assert.throws(function () { [a] = iterable2; }, Error, "Fetch .value throws", "Exception while getting value");
		}
	},    
    {
		name : "Destructuring - .return function can also throw",
		body : function () {
            shouldReturnThrow = true;
            assert.throws(function () { [a] = iterable; }, Error, "Calling .return throws", "Exception from return function");
            shouldReturnThrow = false;
		}
	},    
    {
		name : "Destructuring - caller and .return function both throw and caller's exception wins",
		body : function () {
            shouldReturnThrow = true;
            returnCount = 0;
            assert.throws(function () { [obj.prop] = iterable; }, Error, "Setting value will throw", "From setter");
            assert.areEqual(returnCount, 1, "Ensuring that return function is called");
            shouldReturnThrow = false;
		}
	},    
    {
		name : "Destructuring - with generator",
		body : function () {
            var finallyCount = 0;
            function *gf() {
                try {
                    yield 1;
                    assert.fail('should not reach after yield');
                }
                finally {
                    finallyCount++;
                }
                assert.fail('should not reach after finally');;
            }
            
            [a] = gf();
            assert.areEqual(finallyCount, 1, "Exhausting pattern will call return function on generator, which will execute finally block");
            
            finallyCount = 0;
            assert.throws(function () { [obj.prop] = gf(); }, Error, "Assigning value to destructuring element can throw", "From setter");
            assert.areEqual(finallyCount, 1, "Exception causes to call return function on generator, which will execute finally block");
            
            function* gf2() {
                yield 1;
                assert.fail('should not reach after yield');
            }
            var returnCount = 0;
            gf2.prototype.return = function() {
                returnCount++;
                return {};
            };
            [a] = gf2();
            assert.areEqual(returnCount, 1, "Exhausting pattern will call return function on generator");
            
            gf2.prototype.return = function() {
                returnCount++;
                throw new Error('Exception from return function');
            };
            assert.throws(function () { [a] = gf2(); }, Error, "Return function throws", "Exception from return function");
            returnCount = 0;
            assert.throws(function () { [obj.prop] = gf2(); }, Error, "Exception at destructuring element wins", "From setter");
            assert.areEqual(returnCount, 1, "Exception causes to call return function on generator");
		}
	},    
    {
		name : "Destructuring - at function parameter",
		body : function () {
            nextCount = 0;
            returnCount = 0;
            (function([a, b]) {})(iterable);
            assert.areEqual(nextCount, 2, "next function will be called 2 times to evaluate 2 elements in the pattern");
            assert.areEqual(returnCount, 1, "return function will be called once the pattern exhausts");
            
            shouldReturnThrow = true;
            assert.throws(function () { (function([a, b]) {})(iterable) }, Error, "Calling return function while at param throws", "Exception from return function");
            shouldReturnThrow = false;
		}
	},
    {
		name : "Destructuring - assigning to rest parameter can throw but should not call .return function",
		body : function () {
            function* gf() {
                yield 1;
                yield 2;
            }
            gf.prototype.return = function() {
                assert.fail('return function should not be called');
            };
            
            assert.throws(function () { [...obj.prop] = gf(); }, Error, "Assigning to rest head can throw", "From setter");
		}
	},    
    {
		name : "Destructuring - .next throws during rest assignment but it should not call the .return function",
		body : function () {
            returnCount = 0;
            shouldNextThrow = true;
            assert.throws(function () { var [...a] = iterable; }, Error, "next function throws in the array declaration evaluation", "Exception from next function");
            assert.throws(function () { [...a] = iterable; }, Error, "next function throws in the array expression evaluation", "Exception from next function");
            shouldNextThrow = false;
            assert.areEqual(returnCount, 0, "return function should be called even when the next function throws");
		}
	},    
    {
		name : "Destructuring - has yield as initializer",
		body : function () {
            var returnCalled = 0;
            function *innerGen () { yield undefined; yield 21; }

            innerGen.prototype.return = function () {
                returnCalled++;
                return {};
            };
            
            var x;

            var iter = (function * () {
                ([x = yield] = innerGen());
            }());

            iter.next();
            var iterationResult = iter.next(10);
            assert.areEqual(x, 10, "calling next with value 10 will assign 'x' to 10");
            assert.isTrue(iterationResult.done, "iterator is completed as there is nothing more to yield");
            assert.areEqual(returnCalled, 1, "Destructuring elements exhaust and that should call return function");
		}
	},    
    {
		name : "Destructuring - yield in the pattern and inner return throws",
		body : function () {
            function *innerGen () { yield undefined; }

            innerGen.prototype.return = function () {
                throw new Error('Exception from return function');
            };
            
            var x;

            var iter = (function * () {
                ([x = yield] = innerGen());
                assert.fail('Unreachable code');
            }());

            iter.next();
            assert.throws(function () {
                iter.return();
            }, Error, "calling return on the outer generator will call return on inner generator", 'Exception from return function');
		}
	},
    {
		name : "Destructuring - yield in the pattern and both caller and inner return throws",
		body : function () {
            function *innerGen () { yield undefined;}

            var returnCalled = 0;
            innerGen.prototype.return = function () {
                returnCalled++;
                throw new Error('Exception from return function');
            };
            
            var x;

            var iter = (function * () {
                ([x = yield] = innerGen());
                assert.fail('Unreachable code');
            }());

            iter.next();
            assert.throws(function () {
                iter.throw(new Error('Exception from outer throw'));
            }, Error, "calling throw on the outer generator will call return on inner generator but outer exxception wins", 'Exception from outer throw');

            assert.areEqual(returnCalled, 1, "ensuring that return function is called");
		}
	},
    {
		name : "Destructuring - .return will be called even before .next function is called if yield as return",
		body : function () {
            function *innerGen () { assert.fail('innerGen body is not executed');}

            var returnCalled = 0;
            innerGen.prototype.return = function () {
                returnCalled++;
                return {};
            };
            
            var x;

            var iter = (function * () {
                ([{}[yield]] = innerGen());
                assert.fail('Unreachable code');
            }());

            iter.next();
            iter.return();
            assert.areEqual(returnCalled, 1, "ensuring that return function is called");
		}
	},
    {
		name : "Destructuring - with rest - .return will be called even before .next function is called if yield as return",
		body : function () {
            function *innerGen () { assert.fail('innerGen body is not executed');}

            var returnCalled = 0;
            innerGen.prototype.return = function () {
                returnCalled++;
                return {};
            };
            
            var x;

            var iter = (function * () {
                ([...{}[yield]] = innerGen());
                assert.fail('Unreachable code');
            }());

            iter.next();
            iter.return();
            assert.areEqual(returnCalled, 1, "ensuring that return function is called");
		}
	},
    {
		name : "For..of - validation of calling .return function on abrupt loop break",
		body : function () {
            returnCount = 0;
            for (i of iterable) {
                break;
            }
            assert.areEqual(returnCount, 1, "return function is called as the loop is abruptly completed due to 'break'");

            returnCount = 0;
            (function () {
                for (i of iterable) {
                    return;
                }
            })();
            assert.areEqual(returnCount, 1, "return function is called as the loop is abruptly completed due to 'return'");

            returnCount = 0;
            (function () {
                var loop = true;
                outer2 : while (loop) {
                    loop = false;
                    for (i of iterable) {
                        continue outer2;
                    }
                }
            })();
            assert.areEqual(returnCount, 1, "return function is called as the loop is abruptly completed due to 'continue'");

            returnCount = 0;
            (function () {
                var loop = true;
                outer3 : while (loop) {
                    loop = false;
                    for (i of iterable) {
                        break outer3;
                    }
                }
            })();
            assert.areEqual(returnCount, 1, "return function is called as the loop is abruptly completed due to 'break label'");

            nextCount = 0;
            returnCount = 0;
            assert.throws(function () {
                for (i of iterable) {
                    (function () {
                        throw new Error('break loop by causing an exception');
                    })();
                }
            }, Error);
            assert.areEqual(nextCount, 1, "next function is called once as the loop iterates only once");
            assert.areEqual(returnCount, 1, "return function is called as the loop is abruptly completed due to an exception");
            
		}
	},
    {
		name : "For..of - validation of .return function with nesting pattern",
		body : function () {
            
            nextCount = 0;
            returnCount = 0;
            // Creating recursing iterator. the value is another iterator.
            value1 = iterable;
            (function() {
            for (var iter of iterable) {
                for (var iter2 of iter) {
                    return;
                }
            }
            })();
            
            assert.areEqual(nextCount, 2, "Nested iterators - next function is called 2 times, once for each loop");
            assert.areEqual(returnCount, 2, "Nested iterators - return function is called 2 times as two loops are abruptly completed due to 'return'");
            
            nextCount = 0;
            returnCount = 0;
            assert.throws(function() {
                for (var iter of iterable) {
                    for (var iter2 of iter) {
                        throw new Error('error');
                    }
                }
            }, Error);
            
            assert.areEqual(nextCount, 2, "Nested iterators - next function is called 2 times, once for each loop");
            assert.areEqual(returnCount, 2, "Nested iterators - return function is called 2 times as two loops are abruptly completed due to exception");
		}
	},    
    {
		name : "For..of - change .done to true will not call .return function",
		body : function () {
            returnCount = 0;
            done1 = true;
            for (var i of iterable) {
                assert.fail('This will not reach as the .done is marked to true');
            }
            
            assert.areEqual(returnCount, 0, "Loop is completed as .done is set to true, this ensures that return function should not be called");
            done1 = false;
		}
	},    
    {
		name : "For..of - validating that causing an exception on assigning value to for..of head  will call .return function",
		body : function () {
            returnCount = 0;
            assert.throws(function () {for (obj.prop of iterable) {
                assert.fail('Should not reach here as assigning to obj.prop throws');
            }}, Error, "Assigning to loop head can throw", "From setter");
            
            assert.areEqual(returnCount, 1, "Abrupt loop break due to exception in assigning value to head should call return function");
            
            returnCount = 0;
            value1 = iterable;
            assert.throws(function () {
                for (var iter1 of iterable) {
                    for (obj.prop of iter1) {
                        assert.fail('Should not reach here as assigning to obj.prop throws');
                    }
                }
            }, Error, "Assigning to loop head can throw", "From setter");
            
            assert.areEqual(returnCount, 2, "Exception caused in inner loop when assigning value to head should call the return function");
            value1 = 1;
		}
	},    
    {
		name : "For..of - .return function should not be called when .next function throws",
		body : function () {
            returnCount = 0;
            shouldNextThrow = true;
            assert.throws(function () { for (var i of iterable) { } }, Error, "Calling .next throws", "Exception from next function");
            shouldNextThrow = false;
            assert.areEqual(returnCount, 0, "Ensuring that exception in next function should not call the return function");
		}
	},    
    {
		name : "For..of - .return function should not be called when fetching .value throws",
		body : function () {
            var iterable2 = {};
            iterable2[Symbol.iterator] = function() {
              return {
                next: function() {
                    return {get value () { throw new Error('Exception while getting value'); }, done:false};
                },
                return: function() {
                    assert.fail('return should not be called');
                },
              };
            };

            assert.throws(function () { for (var i of iterable2) { } }, Error,
                ".value causes an exception but that should not call the return function",
                "Exception while getting value");
		}
	},    
    {
		name : "For..of - .return function can also throw",
		body : function () {
            shouldReturnThrow = true;
            assert.throws(function () { for (var i of iterable) { break; } }, Error, ".return function throws", "Exception from return function");
            shouldReturnThrow = false;
		}
	},    
    {
		name : "For..of - caller and .return function both throw and caller's exception wins",
		body : function () {
            shouldReturnThrow = true;
            returnCount = 0;
            assert.throws(function () { for (obj.prop of iterable) { break; } }, Error, "Setting value will throw", "From setter");
            assert.areEqual(returnCount, 1, "Ensuring that abrupt loop completion due to exception will call return function");
            shouldReturnThrow = false;
		}
	},    
    {
		name : "For..of - with generator",
		body : function () {
            var finallyCount = 0;
            function *gf() {
                try {
                    yield 1;
                    assert.fail('Should not reach here after yield');
                }
                finally {
                    finallyCount++;
                }
                assert.fail('Should not reach here after finally');;
            }
            
            for (var i of gf()) {
                break;
            }

            assert.areEqual(finallyCount, 1, "'break' causes the return function called which should execute the finally block");
            
            finallyCount = 0;
            assert.throws(function () { for (obj.prop of gf()) { } }, Error, "Setting value will throw", "From setter");
            assert.areEqual(finallyCount, 1, "Exception causes the return function called which should execute the finally block");
            
            function* gf2() {
                yield 1;
                assert.fail('Should not reach here after yield');
            }
            var returnCount = 0;
            gf2.prototype.return = function() {
                returnCount++;
                return {};
            };
            
            for (var i of gf2()) {
                break;
            }

            assert.areEqual(returnCount, 1, "Loop break due to 'break' should call the return function on the generator");
            
            gf2.prototype.return = function() {
                returnCount++;
                throw new Error('Exception from return function');
            };
            assert.throws(function () { for (i of gf2()) { break; } }, Error, "return function throws", "Exception from return function");
            returnCount = 0;
            assert.throws(function () { for (obj.prop of gf2()) { } }, Error, "Exception at destructuring element wins", "From setter");
            assert.areEqual(returnCount, 1, "Exception in loop should call the return function");
            
		}
	},    
    {
		name : "Iterator close with yield *",
		body : function () {
            var returnCalled = 0;
            let innerGen = function*() { yield 1; yield 2 };
            innerGen.prototype.return = function () {
                returnCalled++;
                return {};
            };
            
            function* gf() { yield* innerGen() }

            for (var i of gf()) {
                break;
            }

            assert.areEqual(returnCalled, 1, "Loop break due to 'break' should call the return function, yield * should propagate that to inner generator");
            
            returnCalled = 0;
            (function() {
                for (var i of gf()) {
                    return;
                }
            })();
            assert.areEqual(returnCalled, 1, "Loop break due to 'return' should call the return function, yield * should propagate that to inner generator");
            
            returnCalled = 0;
            assert.throws(function () { for (var i of gf()) { throw new Error(''); } }, Error);
            assert.areEqual(returnCalled, 1, "Loop break due to 'throw' should call the return function, yield * should propagate that to inner generator");

            returnCalled = 0;
            var [x2] = gf();
            assert.areEqual(returnCalled, 1, "Exhausting destructuring element will call the return function");
		}
	},    
    {
		name : "Array.from - Iterator closing when mapping function throws",
		body : function () {
            var mapFn = function() {
                throw new Error('');
            };
            
            returnCount = 0;
            assert.throws(function () { 
                Array.from(iterable, mapFn);
            }, Error);
    
            assert.areEqual(returnCount, 1, "Exception in mapping function in Array.from should call the return function");
		}
	},    
    {
		name : "Array.from - .return function should not be called when .next function throws",
		body : function () {
            shouldNextThrow = true;
            returnCount = 0;
            assert.throws(function () { 
                Array.from(iterable);
            }, Error);
            assert.areEqual(returnCount, 0, "next function causes exception which should not call the return function");
            shouldNextThrow = false;
		}
	},    
    {
		name : "Array.from - both mapping function and .return throw but the outer exception wins",
		body : function () {
            var mapFn = function() {
                throw new Error('Exception from map function');
            };
            
            returnCount = 0;
            shouldReturnThrow = true;
            assert.throws(function () { 
                Array.from(iterable, mapFn);
            }, Error, 'Validate that the exception from return function is skipped', 'Exception from map function');
    
            shouldReturnThrow = false;
            assert.areEqual(returnCount, 1, "return function is called when mapping function throws");
		}
	},    
    {
		name : "Map - calling .return function in the event of exception",
		body : function () {
            returnCount = 0;
            assert.throws(function () { 
                new Map(iterable);
            }, TypeError);
            assert.areEqual(returnCount, 1, "return function is called when iterator does not return object");
            
            Map.prototype.set = function() {
                throw new Error('');
            };
            
            value1 = [];
            returnCount = 0;
            assert.throws(function () { 
                new Map(iterable);
            }, Error);
    
            assert.areEqual(returnCount, 1, "return function is called when .set function throws");
            value1 = 1;
		}
	},    
    {
		name : "Map - .return function should not be called when .next function throws",
		body : function () {
            returnCount = 0;
            shouldNextThrow = true;
            assert.throws(function () { 
                new Map(iterable);
            }, Error);
    
            assert.areEqual(returnCount, 0, "next function causes exception which should not call the return function");
            shouldNextThrow = false;
		}
	},    
    {
		name : "Map - both .set function and .return throw but the outer exception wins",
		body : function () {
            Map.prototype.set = function() {
                throw new Error('Exception from set function');
            };
            
            value1 = [];
            returnCount = 0;
            shouldReturnThrow = true;
            assert.throws(function () { 
                new Map(iterable);
            }, Error, 'Validate that the exception from return function is skipped', 'Exception from set function');
    
            shouldReturnThrow = true;
            value1 = 1;
            assert.areEqual(returnCount, 1, "return function is called as the set function throws");
		}
	},    
    {
		name : "WeakMap - calling .return function in the event of exception",
		body : function () {
            returnCount = 0;
            assert.throws(function () { 
                new WeakMap(iterable);
            }, TypeError);
            assert.areEqual(returnCount, 1, "return function is called when iterator does not return object");
            
            WeakMap.prototype.set = function() {
                throw new Error('');
            };
            
            value1 = [];
            returnCount = 0;
            assert.throws(function () { 
                new WeakMap(iterable);
            }, Error);
    
            assert.areEqual(returnCount, 1, "return function is called as the set function throws");
            value1 = 1;
		}
	},    
    {
		name : "WeakMap - .return function should not be called when .next function throws",
		body : function () {
            returnCount = 0;
            shouldNextThrow = true;
            assert.throws(function () { 
                new WeakMap(iterable);
            }, Error);
    
            assert.areEqual(returnCount, 0, "next function causes exception which should not call the return function");
            shouldNextThrow = false;
		}
	},    
    {
		name : "WeakMap - both .set function and .return throw but the outer exception wins",
		body : function () {
            WeakMap.prototype.set = function() {
                throw new Error('Exception from set function');
            };
            
            value1 = [];
            returnCount = 0;
            shouldReturnThrow = true;
            assert.throws(function () { 
                new WeakMap(iterable);
            }, Error, 'Validate that the exception from return function is skipped', 'Exception from set function');
    
            shouldReturnThrow = true;
            value1 = 1;
            assert.areEqual(returnCount, 1, "return function is called as the set function throws");
		}
	},    
    {
		name : "Set - calling .return function when .add function throws",
		body : function () {
            Set.prototype.add = function() {
                throw new Error('');
            };
            
            returnCount = 0;
            assert.throws(function () { 
                new Set(iterable);
            }, Error);
    
            assert.areEqual(returnCount, 1, "return function is called as the add function throws");
		}
	},
    {
		name : "Set - .return function should not be called when .next function throws",
		body : function () {
            returnCount = 0;
            shouldNextThrow = true;
            assert.throws(function () { 
                new Set(iterable);
            }, Error);
    
            assert.areEqual(returnCount, 0, "next function causes exception which should not call the return function");
            shouldNextThrow = false;
		}
	},
    {
		name : "Set - both .add function and .return throw but the outer exception wins",
		body : function () {
            Set.prototype.add = function() {
                throw new Error('Exception from add function');
            };
            
            returnCount = 0;
            shouldReturnThrow = true;
            assert.throws(function () { 
                new Set(iterable);
            }, Error, 'Validate that the exception from return function is skipped', 'Exception from add function');
    
            shouldReturnThrow = false;
            assert.areEqual(returnCount, 1, "return function is called as the add function throws");
		}
	},
    {
		name : "WeakSet - calling .return function when .add function throws",
		body : function () {
            WeakSet.prototype.add = function() {
                throw new Error('');
            };
            
            returnCount = 0;
            assert.throws(function () { 
                new WeakSet(iterable);
            }, Error);
    
            assert.areEqual(returnCount, 1, "return function is called as the add function throws");
		}
	},
    {
		name : "WeakSet - .return function should not be called when .next function throws",
		body : function () {
            returnCount = 0;
            shouldNextThrow = true;
            assert.throws(function () { 
                new WeakSet(iterable);
            }, Error);
    
            assert.areEqual(returnCount, 0, "next function causes exception which should not call the return function");
            shouldNextThrow = false;
		}
	},
    {
		name : "WeakSet - both .add function and .return throw but the outer exception wins",
		body : function () {
            WeakSet.prototype.add = function() {
                throw new Error('Exception from add function');
            };
            
            returnCount = 0;
            shouldReturnThrow = true;
            assert.throws(function () { 
                new WeakSet(iterable);
            }, Error, 'Validate that the exception from return function is skipped', 'Exception from add function');
    
            shouldReturnThrow = false;
            assert.areEqual(returnCount, 1, "return function is called as the add function throws");
		}
	},
    {
		name : "Promise.all - call .return function when .resolve function throws",
		body : function () {
            Promise.resolve = function () {
                throw new Error('');
            }
            returnCount = 0;
            Promise.all(iterable);
            assert.areEqual(returnCount, 1, "return function is called as the resolve function throws");
		}
	},    
    {
		name : "Promise.all - .return function should not be called when .next function throws",
		body : function () {
            returnCount = 0;
            shouldNextThrow = true;
            Promise.all(iterable);
            shouldNextThrow = false;
            assert.areEqual(returnCount, 0, "next function causes exception which should not call the return function");
		}
	},    
    {
		name : "Promise.all - both .resolve and .return function thrown and outer exception wins",
		body : function () {
            Promise.resolve = function () {
                throw new Error('Exception from resolve function');
            }
            returnCount = 0;
            shouldReturnThrow = true;
            var p = Promise.all(iterable);
            shouldReturnThrow = false;
            assert.areEqual(returnCount, 1, "return function is called as the resolve function throws");
            p.catch( function (err) {
                assert.areEqual(err.message, 'Exception from resolve function');
            });
		}
	},    
    {
		name : "Promise.race - call .return function when .resolve function throws",
		body : function () {
            Promise.resolve = function () {
                throw new Error('');
            }
            returnCount = 0;
            Promise.race(iterable);
            assert.areEqual(returnCount, 1, "return function is called as the resolve function throws");
		}
	},    
    {
		name : "Promise.race - .return function should not be called when .next function throws",
		body : function () {
            returnCount = 0;
            shouldNextThrow = true;
            Promise.race(iterable);
            shouldNextThrow = false;
            assert.areEqual(returnCount, 0, "next function causes exception which should not call the return function");
		}
	},    
    {
		name : "Promise.race - both .resolve and .return function thrown and outer exception wins",
		body : function () {
            Promise.resolve = function () {
                throw new Error('Exception from resolve function');
            }
            returnCount = 0;
            shouldReturnThrow = true;
            var p = Promise.race(iterable);
            shouldReturnThrow = false;
            assert.areEqual(returnCount, 1, "return function is called as the resolve function throws");
            p.catch( function (err) {
                assert.areEqual(err.message, 'Exception from resolve function');
            });
		}
	},    
    {
		name : "BugFix : yielding in the call expression under generator function",
		body : function () {
            var val = 0;
            function bar(a, b, c) { val = b; }
            function *foo(d) {
                for (var k of [2, 3]) {
                    bar(1, d ? yield : d, k);
                }
            }
            var iter = foo(true);
            iter.next();
            iter.next();
            iter.next(10);
            assert.areEqual(val, 10, "yielding in the call expression under for..of is working correctly");
		}
	},    
    {
		name : "BugFix : yielding in the call expression under try catch",
		body : function () {
            var val = 0;
            var counter = 0;
            function bar(a, b, c) { val = b; }
            function *foo(d) {
                try {
                     try {
                         bar(1, d ? yield : d, 11);
                     } finally {
                         counter++;
                     }
                } finally {
                    counter++;
                }
            }
            var iter = foo(true);
            iter.next();
            iter.next(10);
            assert.areEqual(val, 10, "yielding in the call expression under try/catch is working correctly");
            assert.areEqual(counter, 2, "both finally called after yielding");
		}
	},    
];

testRunner.runTests(tests, {
	verbose : WScript.Arguments[0] != "summary"
});
