iteratorclose.js 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117
  1. //-------------------------------------------------------------------------------------------------------
  2. // Copyright (C) Microsoft. All rights reserved.
  3. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  4. //-------------------------------------------------------------------------------------------------------
  5. WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
  6. var nextCount = 0;
  7. var returnCount = 0;
  8. var value1 = 1;
  9. var done1 = false;
  10. var iterable = {};
  11. var shouldNextThrow = false;
  12. var shouldReturnThrow = false;
  13. iterable[Symbol.iterator] = function () {
  14. return {
  15. next: function() {
  16. nextCount++;
  17. if (shouldNextThrow) { throw new Error('Exception from next function'); }
  18. return {value : value1, done:done1};
  19. },
  20. return: function (value) {
  21. returnCount++;
  22. if (shouldReturnThrow) { throw new Error('Exception from return function'); }
  23. return {done:true};
  24. }
  25. };
  26. };
  27. var obj = {
  28. set prop(val) {
  29. throw new Error('From setter');
  30. }
  31. };
  32. var tests = [
  33. {
  34. name : "Destructuring - no .return function defined is a valid scenario",
  35. body : function () {
  36. var iterable = {};
  37. iterable[Symbol.iterator] = function () {
  38. return {
  39. next : function () {
  40. return {
  41. value : 10,
  42. done : false
  43. };
  44. }
  45. };
  46. };
  47. var [a] = iterable;
  48. assert.areEqual(a, 10, "Destructuring declaration calls next on iterator and should get 10");
  49. var b;
  50. [b] = iterable;
  51. assert.areEqual(b, 10, "Destructuring expression calls next on iterator and should get 10");
  52. }
  53. },
  54. {
  55. name : "Destructuring - validating that the iterable has .return field but not as a function",
  56. body : function () {
  57. var returnFnc = undefined;
  58. var iterable = {};
  59. iterable[Symbol.iterator] = function() {
  60. return {
  61. next: function() {
  62. return {value:10, done:false};
  63. },
  64. return: returnFnc
  65. };
  66. };
  67. var a;
  68. assert.doesNotThrow(function() {
  69. [a] = iterable;
  70. }, "iterator has return field which returns 'undefined' and that should not throw");
  71. assert.doesNotThrow(function() {
  72. returnFnc = null;
  73. [a] = iterable;
  74. }, "iterator has return field which returns 'null' and that should not throw");
  75. assert.throws(function () { returnFnc = {}; [a] = iterable; }, TypeError,
  76. "return field is not a function and returns other than 'undefined'/'null' should throw Type Error");
  77. }
  78. },
  79. {
  80. name : "Destructuring - basic validation with .return function when the pattern is empty or has one element",
  81. body : function () {
  82. nextCount = 0;
  83. returnCount = 0;
  84. [] = iterable;
  85. assert.areEqual(nextCount, 0, "Empty LHS pattern should not call the next function");
  86. assert.areEqual(returnCount, 1, "Evaluation of empty LHS pattern will call return function");
  87. nextCount = 0;
  88. returnCount = 0;
  89. var [] = iterable;
  90. assert.areEqual(nextCount, 0, "Empty LHS declaration pattern should not call the next function");
  91. assert.areEqual(returnCount, 1, "Evaluation of empty LHS declaration pattern call return function");
  92. nextCount = 0;
  93. returnCount = 0;
  94. [,] = iterable;
  95. assert.areEqual(nextCount, 1, "Comma operator causes the next to be called");
  96. assert.areEqual(returnCount, 1, "Exhausting the LHS pattern (after comma) will call the call return function");
  97. var a;
  98. nextCount = 0;
  99. returnCount = 0;
  100. [a] = iterable;
  101. assert.areEqual(nextCount, 1, "Evaluating destructuring element will call next function");
  102. assert.areEqual(returnCount, 1, "Exhausting the LHS pattern (after comma) will call the call return function");
  103. }
  104. },
  105. {
  106. name : "Destructuring - validation with .return function with nesting pattern",
  107. body : function () {
  108. nextCount = 0;
  109. returnCount = 0;
  110. [[a]] = [iterable];
  111. assert.areEqual(nextCount, 1, "Nested array pattern will call next function when 'a' is evauluated");
  112. assert.areEqual(returnCount, 1, "When nested array pattern exhausts the return function will be called");
  113. nextCount = 0;
  114. returnCount = 0;
  115. value1 = iterable;
  116. [a, [a]] = iterable;
  117. assert.areEqual(nextCount, 3, "Recursive iterators on RHS - next function will be called 3 times in order to exhaust elements");
  118. assert.areEqual(returnCount, 2, "Recursive iterators on RHS - return function for two nested iterators will be called");
  119. value1 = 1; // Reset it back
  120. nextCount = 0;
  121. returnCount = 0;
  122. value1 = iterable;
  123. var [a, [a]] = iterable;
  124. assert.areEqual(nextCount, 3, "Array declaration - Recursive iterators on RHS - next function will be called 3 times in order to exhaust elements");
  125. assert.areEqual(returnCount, 2, "Array declaration - Recursive iterators on RHS - return function for two nested iterators will be called");
  126. value1 = 1; // Reset it back
  127. }
  128. },
  129. {
  130. name : "Destructuring - change .done to true will not call .return function",
  131. body : function () {
  132. nextCount = 0;
  133. returnCount = 0;
  134. done1 = true;
  135. var [a] = iterable;
  136. assert.areEqual(nextCount, 1, "next function is called to evaluate the element on the LHS");
  137. assert.areEqual(returnCount, 0, "'done' is set true on next function call - which will ensure that return function is not called");
  138. done1 = false;
  139. }
  140. },
  141. {
  142. name : "Destructuring - validating that exception will call the .return function",
  143. body : function () {
  144. nextCount = 0;
  145. returnCount = 0;
  146. assert.throws(function () { [obj.prop] = iterable; }, Error, "Pattern throws error while assigning .value", "From setter");
  147. assert.areEqual(returnCount, 1, "Abrupt completion due to exception will call the return function");
  148. nextCount = 0;
  149. returnCount = 0;
  150. value1 = iterable;
  151. assert.throws(function () { [k, [obj.prop]] = iterable; }, Error, "Nested pattern throws error while assigning .value", "From setter");
  152. assert.areEqual(returnCount, 2, "Nested recursive iterators - abrupt completion due to exception will call the return function");
  153. value1 = 1;
  154. nextCount = 0;
  155. returnCount = 0;
  156. value1 = iterable;
  157. assert.throws(function () { [[[[obj.prop]]]] = iterable; }, Error, "Nested pattern throws error while assigning .value", "From setter");
  158. assert.areEqual(nextCount, 4, "Nested recursing iterators - 4 times next function called due to 4 level depth of array pattern");
  159. assert.areEqual(returnCount, 4, "Nested recursing iterators - 4 times return function called due to abrupt comletion");
  160. value1 = 1;
  161. done1 = true;
  162. nextCount = 0;
  163. returnCount = 0;
  164. assert.throws(function () { [obj.prop] = iterable; }, Error, "Pattern throws error while assigning .value", "From setter");
  165. assert.areEqual(returnCount, 0, "Ensuring that return function is not called when 'done' is set to true during abrupt completion");
  166. done1 = false; // Reset it back.
  167. }
  168. },
  169. {
  170. name : "Destructuring - chained iterators will have their .return function called correctly",
  171. body : function () {
  172. var nextCount1 = 0;
  173. var nextCount2 = 0;
  174. var returnCount1 = 0;
  175. var returnCount2 = 0;
  176. var iterable = {};
  177. iterable[Symbol.iterator] = function() {
  178. return {
  179. next: function() {
  180. nextCount1++;
  181. return { value: iterable2, done: false };
  182. },
  183. return: function() {
  184. returnCount1++;
  185. return {done:true};
  186. },
  187. };
  188. };
  189. var iterable2 = {};
  190. iterable2[Symbol.iterator] = function() {
  191. return {
  192. next: function() {
  193. nextCount2++;
  194. return { value: [0], done: false };
  195. },
  196. return: function() {
  197. returnCount2++;
  198. return {done:true};
  199. }
  200. };
  201. };
  202. nextCount1 = 0;
  203. nextCount2 = 0;
  204. returnCount1 = 0;
  205. returnCount2 = 0;
  206. assert.throws(function () {[[obj.prop]] = iterable; }, Error, "Pattern throws error while assigning .value", "From setter");
  207. assert.areEqual(nextCount1, 1, "next function for the first iterator is called");
  208. assert.areEqual(nextCount2, 1, "next function for the second iterator is called");
  209. assert.areEqual(returnCount1, 1, "return function for the second iterator is called" );
  210. assert.areEqual(returnCount2, 1, "return function for the first iterator is called");
  211. }
  212. },
  213. {
  214. name : "Destructuring - .return function should not be called when .next function throws",
  215. body : function () {
  216. shouldNextThrow = true;
  217. returnCount = 0;
  218. assert.throws(function () { var [a] = iterable; }, Error, "Array declaration - Calling .next throws", "Exception from next function");
  219. assert.areEqual(returnCount, 0, "Ensuring that return function is not called in array declaration pattern");
  220. assert.throws(function () { [a] = iterable; }, Error, "Array expression - Calling .next throws", "Exception from next function");
  221. assert.areEqual(returnCount, 0, "Ensuring that return function is not called in array expression pattern");
  222. assert.throws(function () { var [...a] = iterable; }, Error, "Array declaration with rest - Calling .next throws", "Exception from next function");
  223. assert.areEqual(returnCount, 0, "Ensuring that return function is not called when evalauting rest in array declaration pattern");
  224. assert.throws(function () { [...a] = iterable; }, Error, "Array expression with rest - Calling .next throws", "Exception from next function");
  225. assert.areEqual(returnCount, 0, "Ensuring that return function is not called when evaluating rest in array expression pattern");
  226. shouldNextThrow = false;
  227. }
  228. },
  229. {
  230. name : "Destructuring - .return function should not be called when fetching .value throws",
  231. body : function () {
  232. var iterable2 = {};
  233. iterable2[Symbol.iterator] = function() {
  234. return {
  235. next: function() {
  236. return {get value () { throw new Error('Exception while getting value'); }, done:false};
  237. },
  238. return: function() {
  239. assert.fail('return should not be called');
  240. return {};
  241. },
  242. };
  243. };
  244. assert.throws(function () { [a] = iterable2; }, Error, "Fetch .value throws", "Exception while getting value");
  245. }
  246. },
  247. {
  248. name : "Destructuring - .return function can also throw",
  249. body : function () {
  250. shouldReturnThrow = true;
  251. assert.throws(function () { [a] = iterable; }, Error, "Calling .return throws", "Exception from return function");
  252. shouldReturnThrow = false;
  253. }
  254. },
  255. {
  256. name : "Destructuring - caller and .return function both throw and caller's exception wins",
  257. body : function () {
  258. shouldReturnThrow = true;
  259. returnCount = 0;
  260. assert.throws(function () { [obj.prop] = iterable; }, Error, "Setting value will throw", "From setter");
  261. assert.areEqual(returnCount, 1, "Ensuring that return function is called");
  262. shouldReturnThrow = false;
  263. }
  264. },
  265. {
  266. name : "Destructuring - with generator",
  267. body : function () {
  268. var finallyCount = 0;
  269. function *gf() {
  270. try {
  271. yield 1;
  272. assert.fail('should not reach after yield');
  273. }
  274. finally {
  275. finallyCount++;
  276. }
  277. assert.fail('should not reach after finally');;
  278. }
  279. [a] = gf();
  280. assert.areEqual(finallyCount, 1, "Exhausting pattern will call return function on generator, which will execute finally block");
  281. finallyCount = 0;
  282. assert.throws(function () { [obj.prop] = gf(); }, Error, "Assigning value to destructuring element can throw", "From setter");
  283. assert.areEqual(finallyCount, 1, "Exception causes to call return function on generator, which will execute finally block");
  284. function* gf2() {
  285. yield 1;
  286. assert.fail('should not reach after yield');
  287. }
  288. var returnCount = 0;
  289. gf2.prototype.return = function() {
  290. returnCount++;
  291. return {};
  292. };
  293. [a] = gf2();
  294. assert.areEqual(returnCount, 1, "Exhausting pattern will call return function on generator");
  295. gf2.prototype.return = function() {
  296. returnCount++;
  297. throw new Error('Exception from return function');
  298. };
  299. assert.throws(function () { [a] = gf2(); }, Error, "Return function throws", "Exception from return function");
  300. returnCount = 0;
  301. assert.throws(function () { [obj.prop] = gf2(); }, Error, "Exception at destructuring element wins", "From setter");
  302. assert.areEqual(returnCount, 1, "Exception causes to call return function on generator");
  303. }
  304. },
  305. {
  306. name : "Destructuring - at function parameter",
  307. body : function () {
  308. nextCount = 0;
  309. returnCount = 0;
  310. (function([a, b]) {})(iterable);
  311. assert.areEqual(nextCount, 2, "next function will be called 2 times to evaluate 2 elements in the pattern");
  312. assert.areEqual(returnCount, 1, "return function will be called once the pattern exhausts");
  313. shouldReturnThrow = true;
  314. assert.throws(function () { (function([a, b]) {})(iterable) }, Error, "Calling return function while at param throws", "Exception from return function");
  315. shouldReturnThrow = false;
  316. }
  317. },
  318. {
  319. name : "Destructuring - assigning to rest parameter can throw but should not call .return function",
  320. body : function () {
  321. function* gf() {
  322. yield 1;
  323. yield 2;
  324. }
  325. gf.prototype.return = function() {
  326. assert.fail('return function should not be called');
  327. };
  328. assert.throws(function () { [...obj.prop] = gf(); }, Error, "Assigning to rest head can throw", "From setter");
  329. }
  330. },
  331. {
  332. name : "Destructuring - .next throws during rest assignment but it should not call the .return function",
  333. body : function () {
  334. returnCount = 0;
  335. shouldNextThrow = true;
  336. assert.throws(function () { var [...a] = iterable; }, Error, "next function throws in the array declaration evaluation", "Exception from next function");
  337. assert.throws(function () { [...a] = iterable; }, Error, "next function throws in the array expression evaluation", "Exception from next function");
  338. shouldNextThrow = false;
  339. assert.areEqual(returnCount, 0, "return function should be called even when the next function throws");
  340. }
  341. },
  342. {
  343. name : "Destructuring - has yield as initializer",
  344. body : function () {
  345. var returnCalled = 0;
  346. function *innerGen () { yield undefined; yield 21; }
  347. innerGen.prototype.return = function () {
  348. returnCalled++;
  349. return {};
  350. };
  351. var x;
  352. var iter = (function * () {
  353. ([x = yield] = innerGen());
  354. }());
  355. iter.next();
  356. var iterationResult = iter.next(10);
  357. assert.areEqual(x, 10, "calling next with value 10 will assign 'x' to 10");
  358. assert.isTrue(iterationResult.done, "iterator is completed as there is nothing more to yield");
  359. assert.areEqual(returnCalled, 1, "Destructuring elements exhaust and that should call return function");
  360. }
  361. },
  362. {
  363. name : "Destructuring - yield in the pattern and inner return throws",
  364. body : function () {
  365. function *innerGen () { yield undefined; }
  366. innerGen.prototype.return = function () {
  367. throw new Error('Exception from return function');
  368. };
  369. var x;
  370. var iter = (function * () {
  371. ([x = yield] = innerGen());
  372. assert.fail('Unreachable code');
  373. }());
  374. iter.next();
  375. assert.throws(function () {
  376. iter.return();
  377. }, Error, "calling return on the outer generator will call return on inner generator", 'Exception from return function');
  378. }
  379. },
  380. {
  381. name : "Destructuring - yield in the pattern and both caller and inner return throws",
  382. body : function () {
  383. function *innerGen () { yield undefined;}
  384. var returnCalled = 0;
  385. innerGen.prototype.return = function () {
  386. returnCalled++;
  387. throw new Error('Exception from return function');
  388. };
  389. var x;
  390. var iter = (function * () {
  391. ([x = yield] = innerGen());
  392. assert.fail('Unreachable code');
  393. }());
  394. iter.next();
  395. assert.throws(function () {
  396. iter.throw(new Error('Exception from outer throw'));
  397. }, Error, "calling throw on the outer generator will call return on inner generator but outer exxception wins", 'Exception from outer throw');
  398. assert.areEqual(returnCalled, 1, "ensuring that return function is called");
  399. }
  400. },
  401. {
  402. name : "Destructuring - .return will be called even before .next function is called if yield as return",
  403. body : function () {
  404. function *innerGen () { assert.fail('innerGen body is not executed');}
  405. var returnCalled = 0;
  406. innerGen.prototype.return = function () {
  407. returnCalled++;
  408. return {};
  409. };
  410. var x;
  411. var iter = (function * () {
  412. ([{}[yield]] = innerGen());
  413. assert.fail('Unreachable code');
  414. }());
  415. iter.next();
  416. iter.return();
  417. assert.areEqual(returnCalled, 1, "ensuring that return function is called");
  418. }
  419. },
  420. {
  421. name : "Destructuring - with rest - .return will be called even before .next function is called if yield as return",
  422. body : function () {
  423. function *innerGen () { assert.fail('innerGen body is not executed');}
  424. var returnCalled = 0;
  425. innerGen.prototype.return = function () {
  426. returnCalled++;
  427. return {};
  428. };
  429. var x;
  430. var iter = (function * () {
  431. ([...{}[yield]] = innerGen());
  432. assert.fail('Unreachable code');
  433. }());
  434. iter.next();
  435. iter.return();
  436. assert.areEqual(returnCalled, 1, "ensuring that return function is called");
  437. }
  438. },
  439. {
  440. name : "For..of - validation of calling .return function on abrupt loop break",
  441. body : function () {
  442. returnCount = 0;
  443. for (i of iterable) {
  444. break;
  445. }
  446. assert.areEqual(returnCount, 1, "return function is called as the loop is abruptly completed due to 'break'");
  447. returnCount = 0;
  448. (function () {
  449. for (i of iterable) {
  450. return;
  451. }
  452. })();
  453. assert.areEqual(returnCount, 1, "return function is called as the loop is abruptly completed due to 'return'");
  454. returnCount = 0;
  455. (function () {
  456. var loop = true;
  457. outer2 : while (loop) {
  458. loop = false;
  459. for (i of iterable) {
  460. continue outer2;
  461. }
  462. }
  463. })();
  464. assert.areEqual(returnCount, 1, "return function is called as the loop is abruptly completed due to 'continue'");
  465. returnCount = 0;
  466. (function () {
  467. var loop = true;
  468. outer3 : while (loop) {
  469. loop = false;
  470. for (i of iterable) {
  471. break outer3;
  472. }
  473. }
  474. })();
  475. assert.areEqual(returnCount, 1, "return function is called as the loop is abruptly completed due to 'break label'");
  476. nextCount = 0;
  477. returnCount = 0;
  478. assert.throws(function () {
  479. for (i of iterable) {
  480. (function () {
  481. throw new Error('break loop by causing an exception');
  482. })();
  483. }
  484. }, Error);
  485. assert.areEqual(nextCount, 1, "next function is called once as the loop iterates only once");
  486. assert.areEqual(returnCount, 1, "return function is called as the loop is abruptly completed due to an exception");
  487. }
  488. },
  489. {
  490. name : "For..of - validation of .return function with nesting pattern",
  491. body : function () {
  492. nextCount = 0;
  493. returnCount = 0;
  494. // Creating recursing iterator. the value is another iterator.
  495. value1 = iterable;
  496. (function() {
  497. for (var iter of iterable) {
  498. for (var iter2 of iter) {
  499. return;
  500. }
  501. }
  502. })();
  503. assert.areEqual(nextCount, 2, "Nested iterators - next function is called 2 times, once for each loop");
  504. assert.areEqual(returnCount, 2, "Nested iterators - return function is called 2 times as two loops are abruptly completed due to 'return'");
  505. nextCount = 0;
  506. returnCount = 0;
  507. assert.throws(function() {
  508. for (var iter of iterable) {
  509. for (var iter2 of iter) {
  510. throw new Error('error');
  511. }
  512. }
  513. }, Error);
  514. assert.areEqual(nextCount, 2, "Nested iterators - next function is called 2 times, once for each loop");
  515. assert.areEqual(returnCount, 2, "Nested iterators - return function is called 2 times as two loops are abruptly completed due to exception");
  516. }
  517. },
  518. {
  519. name : "For..of - change .done to true will not call .return function",
  520. body : function () {
  521. returnCount = 0;
  522. done1 = true;
  523. for (var i of iterable) {
  524. assert.fail('This will not reach as the .done is marked to true');
  525. }
  526. assert.areEqual(returnCount, 0, "Loop is completed as .done is set to true, this ensures that return function should not be called");
  527. done1 = false;
  528. }
  529. },
  530. {
  531. name : "For..of - validating that causing an exception on assigning value to for..of head will call .return function",
  532. body : function () {
  533. returnCount = 0;
  534. assert.throws(function () {for (obj.prop of iterable) {
  535. assert.fail('Should not reach here as assigning to obj.prop throws');
  536. }}, Error, "Assigning to loop head can throw", "From setter");
  537. assert.areEqual(returnCount, 1, "Abrupt loop break due to exception in assigning value to head should call return function");
  538. returnCount = 0;
  539. value1 = iterable;
  540. assert.throws(function () {
  541. for (var iter1 of iterable) {
  542. for (obj.prop of iter1) {
  543. assert.fail('Should not reach here as assigning to obj.prop throws');
  544. }
  545. }
  546. }, Error, "Assigning to loop head can throw", "From setter");
  547. assert.areEqual(returnCount, 2, "Exception caused in inner loop when assigning value to head should call the return function");
  548. value1 = 1;
  549. }
  550. },
  551. {
  552. name : "For..of - .return function should not be called when .next function throws",
  553. body : function () {
  554. returnCount = 0;
  555. shouldNextThrow = true;
  556. assert.throws(function () { for (var i of iterable) { } }, Error, "Calling .next throws", "Exception from next function");
  557. shouldNextThrow = false;
  558. assert.areEqual(returnCount, 0, "Ensuring that exception in next function should not call the return function");
  559. }
  560. },
  561. {
  562. name : "For..of - .return function should not be called when fetching .value throws",
  563. body : function () {
  564. var iterable2 = {};
  565. iterable2[Symbol.iterator] = function() {
  566. return {
  567. next: function() {
  568. return {get value () { throw new Error('Exception while getting value'); }, done:false};
  569. },
  570. return: function() {
  571. assert.fail('return should not be called');
  572. },
  573. };
  574. };
  575. assert.throws(function () { for (var i of iterable2) { } }, Error,
  576. ".value causes an exception but that should not call the return function",
  577. "Exception while getting value");
  578. }
  579. },
  580. {
  581. name : "For..of - .return function can also throw",
  582. body : function () {
  583. shouldReturnThrow = true;
  584. assert.throws(function () { for (var i of iterable) { break; } }, Error, ".return function throws", "Exception from return function");
  585. shouldReturnThrow = false;
  586. }
  587. },
  588. {
  589. name : "For..of - caller and .return function both throw and caller's exception wins",
  590. body : function () {
  591. shouldReturnThrow = true;
  592. returnCount = 0;
  593. assert.throws(function () { for (obj.prop of iterable) { break; } }, Error, "Setting value will throw", "From setter");
  594. assert.areEqual(returnCount, 1, "Ensuring that abrupt loop completion due to exception will call return function");
  595. shouldReturnThrow = false;
  596. }
  597. },
  598. {
  599. name : "For..of - with generator",
  600. body : function () {
  601. var finallyCount = 0;
  602. function *gf() {
  603. try {
  604. yield 1;
  605. assert.fail('Should not reach here after yield');
  606. }
  607. finally {
  608. finallyCount++;
  609. }
  610. assert.fail('Should not reach here after finally');;
  611. }
  612. for (var i of gf()) {
  613. break;
  614. }
  615. assert.areEqual(finallyCount, 1, "'break' causes the return function called which should execute the finally block");
  616. finallyCount = 0;
  617. assert.throws(function () { for (obj.prop of gf()) { } }, Error, "Setting value will throw", "From setter");
  618. assert.areEqual(finallyCount, 1, "Exception causes the return function called which should execute the finally block");
  619. function* gf2() {
  620. yield 1;
  621. assert.fail('Should not reach here after yield');
  622. }
  623. var returnCount = 0;
  624. gf2.prototype.return = function() {
  625. returnCount++;
  626. return {};
  627. };
  628. for (var i of gf2()) {
  629. break;
  630. }
  631. assert.areEqual(returnCount, 1, "Loop break due to 'break' should call the return function on the generator");
  632. gf2.prototype.return = function() {
  633. returnCount++;
  634. throw new Error('Exception from return function');
  635. };
  636. assert.throws(function () { for (i of gf2()) { break; } }, Error, "return function throws", "Exception from return function");
  637. returnCount = 0;
  638. assert.throws(function () { for (obj.prop of gf2()) { } }, Error, "Exception at destructuring element wins", "From setter");
  639. assert.areEqual(returnCount, 1, "Exception in loop should call the return function");
  640. }
  641. },
  642. {
  643. name : "Iterator close with yield *",
  644. body : function () {
  645. var returnCalled = 0;
  646. let innerGen = function*() { yield 1; yield 2 };
  647. innerGen.prototype.return = function () {
  648. returnCalled++;
  649. return {};
  650. };
  651. function* gf() { yield* innerGen() }
  652. for (var i of gf()) {
  653. break;
  654. }
  655. assert.areEqual(returnCalled, 1, "Loop break due to 'break' should call the return function, yield * should propagate that to inner generator");
  656. returnCalled = 0;
  657. (function() {
  658. for (var i of gf()) {
  659. return;
  660. }
  661. })();
  662. assert.areEqual(returnCalled, 1, "Loop break due to 'return' should call the return function, yield * should propagate that to inner generator");
  663. returnCalled = 0;
  664. assert.throws(function () { for (var i of gf()) { throw new Error(''); } }, Error);
  665. assert.areEqual(returnCalled, 1, "Loop break due to 'throw' should call the return function, yield * should propagate that to inner generator");
  666. returnCalled = 0;
  667. var [x2] = gf();
  668. assert.areEqual(returnCalled, 1, "Exhausting destructuring element will call the return function");
  669. }
  670. },
  671. {
  672. name : "Array.from - Iterator closing when mapping function throws",
  673. body : function () {
  674. var mapFn = function() {
  675. throw new Error('');
  676. };
  677. returnCount = 0;
  678. assert.throws(function () {
  679. Array.from(iterable, mapFn);
  680. }, Error);
  681. assert.areEqual(returnCount, 1, "Exception in mapping function in Array.from should call the return function");
  682. }
  683. },
  684. {
  685. name : "Array.from - .return function should not be called when .next function throws",
  686. body : function () {
  687. shouldNextThrow = true;
  688. returnCount = 0;
  689. assert.throws(function () {
  690. Array.from(iterable);
  691. }, Error);
  692. assert.areEqual(returnCount, 0, "next function causes exception which should not call the return function");
  693. shouldNextThrow = false;
  694. }
  695. },
  696. {
  697. name : "Array.from - both mapping function and .return throw but the outer exception wins",
  698. body : function () {
  699. var mapFn = function() {
  700. throw new Error('Exception from map function');
  701. };
  702. returnCount = 0;
  703. shouldReturnThrow = true;
  704. assert.throws(function () {
  705. Array.from(iterable, mapFn);
  706. }, Error, 'Validate that the exception from return function is skipped', 'Exception from map function');
  707. shouldReturnThrow = false;
  708. assert.areEqual(returnCount, 1, "return function is called when mapping function throws");
  709. }
  710. },
  711. {
  712. name : "Map - calling .return function in the event of exception",
  713. body : function () {
  714. returnCount = 0;
  715. assert.throws(function () {
  716. new Map(iterable);
  717. }, TypeError);
  718. assert.areEqual(returnCount, 1, "return function is called when iterator does not return object");
  719. Map.prototype.set = function() {
  720. throw new Error('');
  721. };
  722. value1 = [];
  723. returnCount = 0;
  724. assert.throws(function () {
  725. new Map(iterable);
  726. }, Error);
  727. assert.areEqual(returnCount, 1, "return function is called when .set function throws");
  728. value1 = 1;
  729. }
  730. },
  731. {
  732. name : "Map - .return function should not be called when .next function throws",
  733. body : function () {
  734. returnCount = 0;
  735. shouldNextThrow = true;
  736. assert.throws(function () {
  737. new Map(iterable);
  738. }, Error);
  739. assert.areEqual(returnCount, 0, "next function causes exception which should not call the return function");
  740. shouldNextThrow = false;
  741. }
  742. },
  743. {
  744. name : "Map - both .set function and .return throw but the outer exception wins",
  745. body : function () {
  746. Map.prototype.set = function() {
  747. throw new Error('Exception from set function');
  748. };
  749. value1 = [];
  750. returnCount = 0;
  751. shouldReturnThrow = true;
  752. assert.throws(function () {
  753. new Map(iterable);
  754. }, Error, 'Validate that the exception from return function is skipped', 'Exception from set function');
  755. shouldReturnThrow = true;
  756. value1 = 1;
  757. assert.areEqual(returnCount, 1, "return function is called as the set function throws");
  758. }
  759. },
  760. {
  761. name : "WeakMap - calling .return function in the event of exception",
  762. body : function () {
  763. returnCount = 0;
  764. assert.throws(function () {
  765. new WeakMap(iterable);
  766. }, TypeError);
  767. assert.areEqual(returnCount, 1, "return function is called when iterator does not return object");
  768. WeakMap.prototype.set = function() {
  769. throw new Error('');
  770. };
  771. value1 = [];
  772. returnCount = 0;
  773. assert.throws(function () {
  774. new WeakMap(iterable);
  775. }, Error);
  776. assert.areEqual(returnCount, 1, "return function is called as the set function throws");
  777. value1 = 1;
  778. }
  779. },
  780. {
  781. name : "WeakMap - .return function should not be called when .next function throws",
  782. body : function () {
  783. returnCount = 0;
  784. shouldNextThrow = true;
  785. assert.throws(function () {
  786. new WeakMap(iterable);
  787. }, Error);
  788. assert.areEqual(returnCount, 0, "next function causes exception which should not call the return function");
  789. shouldNextThrow = false;
  790. }
  791. },
  792. {
  793. name : "WeakMap - both .set function and .return throw but the outer exception wins",
  794. body : function () {
  795. WeakMap.prototype.set = function() {
  796. throw new Error('Exception from set function');
  797. };
  798. value1 = [];
  799. returnCount = 0;
  800. shouldReturnThrow = true;
  801. assert.throws(function () {
  802. new WeakMap(iterable);
  803. }, Error, 'Validate that the exception from return function is skipped', 'Exception from set function');
  804. shouldReturnThrow = true;
  805. value1 = 1;
  806. assert.areEqual(returnCount, 1, "return function is called as the set function throws");
  807. }
  808. },
  809. {
  810. name : "Set - calling .return function when .add function throws",
  811. body : function () {
  812. Set.prototype.add = function() {
  813. throw new Error('');
  814. };
  815. returnCount = 0;
  816. assert.throws(function () {
  817. new Set(iterable);
  818. }, Error);
  819. assert.areEqual(returnCount, 1, "return function is called as the add function throws");
  820. }
  821. },
  822. {
  823. name : "Set - .return function should not be called when .next function throws",
  824. body : function () {
  825. returnCount = 0;
  826. shouldNextThrow = true;
  827. assert.throws(function () {
  828. new Set(iterable);
  829. }, Error);
  830. assert.areEqual(returnCount, 0, "next function causes exception which should not call the return function");
  831. shouldNextThrow = false;
  832. }
  833. },
  834. {
  835. name : "Set - both .add function and .return throw but the outer exception wins",
  836. body : function () {
  837. Set.prototype.add = function() {
  838. throw new Error('Exception from add function');
  839. };
  840. returnCount = 0;
  841. shouldReturnThrow = true;
  842. assert.throws(function () {
  843. new Set(iterable);
  844. }, Error, 'Validate that the exception from return function is skipped', 'Exception from add function');
  845. shouldReturnThrow = false;
  846. assert.areEqual(returnCount, 1, "return function is called as the add function throws");
  847. }
  848. },
  849. {
  850. name : "WeakSet - calling .return function when .add function throws",
  851. body : function () {
  852. WeakSet.prototype.add = function() {
  853. throw new Error('');
  854. };
  855. returnCount = 0;
  856. assert.throws(function () {
  857. new WeakSet(iterable);
  858. }, Error);
  859. assert.areEqual(returnCount, 1, "return function is called as the add function throws");
  860. }
  861. },
  862. {
  863. name : "WeakSet - .return function should not be called when .next function throws",
  864. body : function () {
  865. returnCount = 0;
  866. shouldNextThrow = true;
  867. assert.throws(function () {
  868. new WeakSet(iterable);
  869. }, Error);
  870. assert.areEqual(returnCount, 0, "next function causes exception which should not call the return function");
  871. shouldNextThrow = false;
  872. }
  873. },
  874. {
  875. name : "WeakSet - both .add function and .return throw but the outer exception wins",
  876. body : function () {
  877. WeakSet.prototype.add = function() {
  878. throw new Error('Exception from add function');
  879. };
  880. returnCount = 0;
  881. shouldReturnThrow = true;
  882. assert.throws(function () {
  883. new WeakSet(iterable);
  884. }, Error, 'Validate that the exception from return function is skipped', 'Exception from add function');
  885. shouldReturnThrow = false;
  886. assert.areEqual(returnCount, 1, "return function is called as the add function throws");
  887. }
  888. },
  889. {
  890. name : "Promise.all - call .return function when .resolve function throws",
  891. body : function () {
  892. Promise.resolve = function () {
  893. throw new Error('');
  894. }
  895. returnCount = 0;
  896. Promise.all(iterable);
  897. assert.areEqual(returnCount, 1, "return function is called as the resolve function throws");
  898. }
  899. },
  900. {
  901. name : "Promise.all - .return function should not be called when .next function throws",
  902. body : function () {
  903. returnCount = 0;
  904. shouldNextThrow = true;
  905. Promise.all(iterable);
  906. shouldNextThrow = false;
  907. assert.areEqual(returnCount, 0, "next function causes exception which should not call the return function");
  908. }
  909. },
  910. {
  911. name : "Promise.all - both .resolve and .return function thrown and outer exception wins",
  912. body : function () {
  913. Promise.resolve = function () {
  914. throw new Error('Exception from resolve function');
  915. }
  916. returnCount = 0;
  917. shouldReturnThrow = true;
  918. var p = Promise.all(iterable);
  919. shouldReturnThrow = false;
  920. assert.areEqual(returnCount, 1, "return function is called as the resolve function throws");
  921. p.catch( function (err) {
  922. assert.areEqual(err.message, 'Exception from resolve function');
  923. });
  924. }
  925. },
  926. {
  927. name : "Promise.race - call .return function when .resolve function throws",
  928. body : function () {
  929. Promise.resolve = function () {
  930. throw new Error('');
  931. }
  932. returnCount = 0;
  933. Promise.race(iterable);
  934. assert.areEqual(returnCount, 1, "return function is called as the resolve function throws");
  935. }
  936. },
  937. {
  938. name : "Promise.race - .return function should not be called when .next function throws",
  939. body : function () {
  940. returnCount = 0;
  941. shouldNextThrow = true;
  942. Promise.race(iterable);
  943. shouldNextThrow = false;
  944. assert.areEqual(returnCount, 0, "next function causes exception which should not call the return function");
  945. }
  946. },
  947. {
  948. name : "Promise.race - both .resolve and .return function thrown and outer exception wins",
  949. body : function () {
  950. Promise.resolve = function () {
  951. throw new Error('Exception from resolve function');
  952. }
  953. returnCount = 0;
  954. shouldReturnThrow = true;
  955. var p = Promise.race(iterable);
  956. shouldReturnThrow = false;
  957. assert.areEqual(returnCount, 1, "return function is called as the resolve function throws");
  958. p.catch( function (err) {
  959. assert.areEqual(err.message, 'Exception from resolve function');
  960. });
  961. }
  962. },
  963. {
  964. name : "BugFix : yielding in the call expression under generator function",
  965. body : function () {
  966. var val = 0;
  967. function bar(a, b, c) { val = b; }
  968. function *foo(d) {
  969. for (var k of [2, 3]) {
  970. bar(1, d ? yield : d, k);
  971. }
  972. }
  973. var iter = foo(true);
  974. iter.next();
  975. iter.next();
  976. iter.next(10);
  977. assert.areEqual(val, 10, "yielding in the call expression under for..of is working correctly");
  978. }
  979. },
  980. {
  981. name : "BugFix : yielding in the call expression under try catch",
  982. body : function () {
  983. var val = 0;
  984. var counter = 0;
  985. function bar(a, b, c) { val = b; }
  986. function *foo(d) {
  987. try {
  988. try {
  989. bar(1, d ? yield : d, 11);
  990. } finally {
  991. counter++;
  992. }
  993. } finally {
  994. counter++;
  995. }
  996. }
  997. var iter = foo(true);
  998. iter.next();
  999. iter.next(10);
  1000. assert.areEqual(val, 10, "yielding in the call expression under try/catch is working correctly");
  1001. assert.areEqual(counter, 2, "both finally called after yielding");
  1002. }
  1003. },
  1004. ];
  1005. testRunner.runTests(tests, {
  1006. verbose : WScript.Arguments[0] != "summary"
  1007. });