forked from mirrors/gecko-dev
		
	 5dec0e0beb
			
		
	
	
		5dec0e0beb
		
	
	
	
	
		
			
			This patch was autogenerated by my decomponents.py
It covers almost every file with the extension js, jsm, html, py,
xhtml, or xul.
It removes blank lines after removed lines, when the removed lines are
preceded by either blank lines or the start of a new block. The "start
of a new block" is defined fairly hackily: either the line starts with
//, ends with */, ends with {, <![CDATA[, """ or '''. The first two
cover comments, the third one covers JS, the fourth covers JS embedded
in XUL, and the final two cover JS embedded in Python. This also
applies if the removed line was the first line of the file.
It covers the pattern matching cases like "var {classes: Cc,
interfaces: Ci, utils: Cu, results: Cr} = Components;". It'll remove
the entire thing if they are all either Ci, Cr, Cc or Cu, or it will
remove the appropriate ones and leave the residue behind. If there's
only one behind, then it will turn it into a normal, non-pattern
matching variable definition. (For instance, "const { classes: Cc,
Constructor: CC, interfaces: Ci, utils: Cu } = Components" becomes
"const CC = Components.Constructor".)
MozReview-Commit-ID: DeSHcClQ7cG
--HG--
extra : rebase_source : d9c41878036c1ef7766ef5e91a7005025bc1d72b
		
	
			
		
			
				
	
	
		
			584 lines
		
	
	
	
		
			17 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			584 lines
		
	
	
	
		
			17 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* Any copyright is dedicated to the Public Domain.
 | |
|    http://creativecommons.org/publicdomain/zero/1.0/ */
 | |
| 
 | |
| /**
 | |
|  * This file tests the Task.jsm module.
 | |
|  */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| /// Globals
 | |
| 
 | |
| ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 | |
| 
 | |
| ChromeUtils.defineModuleGetter(this, "Services",
 | |
|                                "resource://gre/modules/Services.jsm");
 | |
| ChromeUtils.defineModuleGetter(this, "Task",
 | |
|                                "resource://gre/modules/Task.jsm");
 | |
| 
 | |
| /**
 | |
|  * Returns a promise that will be resolved with the given value, when an event
 | |
|  * posted on the event loop of the main thread is processed.
 | |
|  */
 | |
| function promiseResolvedLater(aValue) {
 | |
|   return new Promise(resolve => {
 | |
|     Services.tm.dispatchToMainThread(() => resolve(aValue));
 | |
|   });
 | |
| }
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| /// Tests
 | |
| 
 | |
| function run_test()
 | |
| {
 | |
|   run_next_test();
 | |
| }
 | |
| 
 | |
| add_test(function test_spawn_primitive()
 | |
| {
 | |
|   function fibonacci(n) {
 | |
|     return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
 | |
|   };
 | |
| 
 | |
|   // Polymorphism between task and non-task functions (see "test_recursion").
 | |
|   Task.spawn(fibonacci(6)).then(function (result) {
 | |
|     Assert.equal(8, result);
 | |
|     run_next_test();
 | |
|   }, function (ex) {
 | |
|     do_throw("Unexpected error: " + ex);
 | |
|   });
 | |
| });
 | |
| 
 | |
| add_test(function test_spawn_function()
 | |
| {
 | |
|   Task.spawn(function () {
 | |
|     return "This is not a generator.";
 | |
|   }).then(function (result) {
 | |
|     Assert.equal("This is not a generator.", result);
 | |
|     run_next_test();
 | |
|   }, function (ex) {
 | |
|     do_throw("Unexpected error: " + ex);
 | |
|   });
 | |
| });
 | |
| 
 | |
| add_test(function test_spawn_function_this()
 | |
| {
 | |
|   Task.spawn(function () {
 | |
|     return this;
 | |
|   }).then(function (result) {
 | |
|     // Since the task function wasn't defined in strict mode, its "this" object
 | |
|     // should be the same as the "this" object in this function, i.e. the global
 | |
|     // object.
 | |
|     Assert.equal(result, this);
 | |
|     run_next_test();
 | |
|   }, function (ex) {
 | |
|     do_throw("Unexpected error: " + ex);
 | |
|   });
 | |
| });
 | |
| 
 | |
| add_test(function test_spawn_function_this_strict()
 | |
| {
 | |
|   "use strict";
 | |
|   Task.spawn(function () {
 | |
|     return this;
 | |
|   }).then(function (result) {
 | |
|     // Since the task function was defined in strict mode, its "this" object
 | |
|     // should be undefined.
 | |
|     Assert.equal(typeof(result), "undefined");
 | |
|     run_next_test();
 | |
|   }, function (ex) {
 | |
|     do_throw("Unexpected error: " + ex);
 | |
|   });
 | |
| });
 | |
| 
 | |
| add_test(function test_spawn_function_returning_promise()
 | |
| {
 | |
|   Task.spawn(function () {
 | |
|     return promiseResolvedLater("Resolution value.");
 | |
|   }).then(function (result) {
 | |
|     Assert.equal("Resolution value.", result);
 | |
|     run_next_test();
 | |
|   }, function (ex) {
 | |
|     do_throw("Unexpected error: " + ex);
 | |
|   });
 | |
| });
 | |
| 
 | |
| add_test(function test_spawn_function_exceptions()
 | |
| {
 | |
|   Task.spawn(function () {
 | |
|     throw new Error("Exception uncaught by task.");
 | |
|   }).then(function (result) {
 | |
|     do_throw("Unexpected success!");
 | |
|   }, function (ex) {
 | |
|     Assert.equal("Exception uncaught by task.", ex.message);
 | |
|     run_next_test();
 | |
|   });
 | |
| });
 | |
| 
 | |
| add_test(function test_spawn_function_taskresult()
 | |
| {
 | |
|   Task.spawn(function () {
 | |
|     throw new Task.Result("Task result");
 | |
|   }).then(function (result) {
 | |
|     Assert.equal("Task result", result);
 | |
|     run_next_test();
 | |
|   }, function (ex) {
 | |
|     do_throw("Unexpected error: " + ex);
 | |
|   });
 | |
| });
 | |
| 
 | |
| add_test(function test_yielded_undefined()
 | |
| {
 | |
|   Task.spawn(function* () {
 | |
|     yield;
 | |
|     return "We continued correctly.";
 | |
|   }).then(function (result) {
 | |
|     Assert.equal("We continued correctly.", result);
 | |
|     run_next_test();
 | |
|   }, function (ex) {
 | |
|     do_throw("Unexpected error: " + ex);
 | |
|   });
 | |
| });
 | |
| 
 | |
| add_test(function test_yielded_primitive()
 | |
| {
 | |
|   Task.spawn(function* () {
 | |
|     return "Primitive " + (yield "value.");
 | |
|   }).then(function (result) {
 | |
|     Assert.equal("Primitive value.", result);
 | |
|     run_next_test();
 | |
|   }, function (ex) {
 | |
|     do_throw("Unexpected error: " + ex);
 | |
|   });
 | |
| });
 | |
| 
 | |
| add_test(function test_star_normal()
 | |
| {
 | |
|   Task.spawn(function* () {
 | |
|     let result = yield Promise.resolve("Value");
 | |
|     for (let i = 0; i < 3; i++) {
 | |
|       result += yield promiseResolvedLater("!");
 | |
|     }
 | |
|     return "Task result: " + result;
 | |
|   }).then(function (result) {
 | |
|     Assert.equal("Task result: Value!!!", result);
 | |
|     run_next_test();
 | |
|   }, function (ex) {
 | |
|     do_throw("Unexpected error: " + ex);
 | |
|   });
 | |
| });
 | |
| 
 | |
| add_test(function test_star_exceptions()
 | |
| {
 | |
|   Task.spawn(function* () {
 | |
|     try {
 | |
|       yield Promise.reject("Rejection result by promise.");
 | |
|       do_throw("Exception expected because the promise was rejected.");
 | |
|     } catch (ex) {
 | |
|       // We catch this exception now, we will throw a different one later.
 | |
|       Assert.equal("Rejection result by promise.", ex);
 | |
|     }
 | |
|     throw new Error("Exception uncaught by task.");
 | |
|   }).then(function (result) {
 | |
|     do_throw("Unexpected success!");
 | |
|   }, function (ex) {
 | |
|     Assert.equal("Exception uncaught by task.", ex.message);
 | |
|     run_next_test();
 | |
|   });
 | |
| });
 | |
| 
 | |
| add_test(function test_star_recursion()
 | |
| {
 | |
|   function* task_fibonacci(n) {
 | |
|     return n < 2 ? n : (yield task_fibonacci(n - 1)) +
 | |
|                        (yield task_fibonacci(n - 2));
 | |
|   };
 | |
| 
 | |
|   Task.spawn(task_fibonacci(6)).then(function (result) {
 | |
|     Assert.equal(8, result);
 | |
|     run_next_test();
 | |
|   }, function (ex) {
 | |
|     do_throw("Unexpected error: " + ex);
 | |
|   });
 | |
| });
 | |
| 
 | |
| add_test(function test_nested_star()
 | |
| {
 | |
|   Task.spawn(function* () {
 | |
|     return yield (function* () {
 | |
|       return yield 5;
 | |
|     })();
 | |
|   }).then(function (result) {
 | |
|     Assert.equal(5, result);
 | |
|     run_next_test();
 | |
|   }, function (ex) {
 | |
|     do_throw("Unexpected error: " + ex);
 | |
|   });
 | |
| });
 | |
| 
 | |
| add_test(function test_async_function_from_generator()
 | |
| {
 | |
|   Task.spawn(function* () {
 | |
|     let object = {
 | |
|       asyncFunction: Task.async(function* (param) {
 | |
|         Assert.equal(this, object);
 | |
|         return param;
 | |
|       })
 | |
|     };
 | |
| 
 | |
|     // Ensure the async function returns a promise that resolves as expected.
 | |
|     Assert.equal((yield object.asyncFunction(1)), 1);
 | |
| 
 | |
|     // Ensure a second call to the async function also returns such a promise.
 | |
|     Assert.equal((yield object.asyncFunction(3)), 3);
 | |
|   }).then(function () {
 | |
|     run_next_test();
 | |
|   }, function (ex) {
 | |
|     do_throw("Unexpected error: " + ex);
 | |
|   });
 | |
| });
 | |
| 
 | |
| add_test(function test_async_function_from_function()
 | |
| {
 | |
|   Task.spawn(function* () {
 | |
|     return Task.spawn(function* () {
 | |
|       let object = {
 | |
|         asyncFunction: Task.async(function (param) {
 | |
|           Assert.equal(this, object);
 | |
|           return param;
 | |
|         })
 | |
|       };
 | |
| 
 | |
|       // Ensure the async function returns a promise that resolves as expected.
 | |
|       Assert.equal((yield object.asyncFunction(5)), 5);
 | |
| 
 | |
|       // Ensure a second call to the async function also returns such a promise.
 | |
|       Assert.equal((yield object.asyncFunction(7)), 7);
 | |
|     });
 | |
|   }).then(function () {
 | |
|     run_next_test();
 | |
|   }, function (ex) {
 | |
|     do_throw("Unexpected error: " + ex);
 | |
|   });
 | |
| });
 | |
| 
 | |
| add_test(function test_async_function_that_throws_rejects_promise()
 | |
| {
 | |
|   Task.spawn(function* () {
 | |
|     let object = {
 | |
|       asyncFunction: Task.async(function* () {
 | |
|         throw "Rejected!";
 | |
|       })
 | |
|     };
 | |
| 
 | |
|     yield object.asyncFunction();
 | |
|   }).then(function () {
 | |
|     do_throw("unexpected success calling async function that throws error");
 | |
|   }, function (ex) {
 | |
|     Assert.equal(ex, "Rejected!");
 | |
|     run_next_test();
 | |
|   });
 | |
| });
 | |
| 
 | |
| add_test(function test_async_return_function()
 | |
| {
 | |
|   Task.spawn(function* () {
 | |
|     // Ensure an async function that returns a function resolves to the function
 | |
|     // itself instead of calling the function and resolving to its return value.
 | |
|     return Task.spawn(function* () {
 | |
|       let returnValue = function () {
 | |
|         return "These aren't the droids you're looking for.";
 | |
|       };
 | |
| 
 | |
|       let asyncFunction = Task.async(function () {
 | |
|         return returnValue;
 | |
|       });
 | |
| 
 | |
|       Assert.equal((yield asyncFunction()), returnValue);
 | |
|     });
 | |
|   }).then(function () {
 | |
|     run_next_test();
 | |
|   }, function (ex) {
 | |
|     do_throw("Unexpected error: " + ex);
 | |
|   });
 | |
| });
 | |
| 
 | |
| add_test(function test_async_throw_argument_not_function()
 | |
| {
 | |
|   Task.spawn(function* () {
 | |
|     // Ensure Task.async throws if its aTask argument is not a function.
 | |
|     Assert.throws(() => Task.async("not a function"),
 | |
|                   /aTask argument must be a function/);
 | |
|   }).then(function () {
 | |
|     run_next_test();
 | |
|   }, function (ex) {
 | |
|     do_throw("Unexpected error: " + ex);
 | |
|   });
 | |
| });
 | |
| 
 | |
| add_test(function test_async_throw_on_function_in_place_of_promise()
 | |
| {
 | |
|   Task.spawn(function* () {
 | |
|     // Ensure Task.spawn throws if passed an async function.
 | |
|     Assert.throws(() => Task.spawn(Task.async(function* () {})),
 | |
|                   /Cannot use an async function in place of a promise/);
 | |
|   }).then(function () {
 | |
|     run_next_test();
 | |
|   }, function (ex) {
 | |
|     do_throw("Unexpected error: " + ex);
 | |
|   });
 | |
| });
 | |
| 
 | |
| 
 | |
| ////////////////// Test rewriting of stack traces
 | |
| 
 | |
| // Backup Task.Debuggin.maintainStack.
 | |
| // Will be restored by `exit_stack_tests`.
 | |
| var maintainStack;
 | |
| add_test(function enter_stack_tests() {
 | |
|   maintainStack = Task.Debugging.maintainStack;
 | |
|   Task.Debugging.maintainStack = true;
 | |
|   run_next_test();
 | |
| });
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Ensure that a list of frames appear in a stack, in the right order
 | |
|  */
 | |
| function do_check_rewritten_stack(frames, ex) {
 | |
|   info("Checking that the expected frames appear in the right order");
 | |
|   info(frames.join(", "));
 | |
|   let stack = ex.stack;
 | |
|   info(stack);
 | |
| 
 | |
|   let framesFound = 0;
 | |
|   let lineNumber = 0;
 | |
|   let reLine = /([^\r\n])+/g;
 | |
|   let match;
 | |
|   while (framesFound < frames.length && (match = reLine.exec(stack))) {
 | |
|     let line = match[0];
 | |
|     let frame = frames[framesFound];
 | |
|     info("Searching for " + frame + " in line " + line);
 | |
|     if (line.includes(frame)) {
 | |
|       info("Found " + frame);
 | |
|       ++framesFound;
 | |
|     } else {
 | |
|       info("Didn't find " + frame);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (framesFound >= frames.length) {
 | |
|     return;
 | |
|   }
 | |
|   do_throw("Did not find: " + frames.slice(framesFound).join(", ") +
 | |
|            " in " + stack.substr(reLine.lastIndex));
 | |
| 
 | |
|   info("Ensuring that we have removed Task.jsm, Promise.jsm");
 | |
|   Assert.ok(!stack.includes("Task.jsm"));
 | |
|   Assert.ok(!stack.includes("Promise.jsm"));
 | |
|   Assert.ok(!stack.includes("Promise-backend.js"));
 | |
| }
 | |
| 
 | |
| 
 | |
| // Test that we get an acceptable rewritten stack when we launch
 | |
| // an error in a Task.spawn.
 | |
| add_test(function test_spawn_throw_stack() {
 | |
|   Task.spawn(function* task_spawn_throw_stack() {
 | |
|     for (let i = 0; i < 5; ++i) {
 | |
|       yield Promise.resolve(); // Without stack rewrite, this would lose valuable information
 | |
|     }
 | |
|     throw new Error("BOOM");
 | |
|   }).then(do_throw, function(ex) {
 | |
|     do_check_rewritten_stack(["task_spawn_throw_stack",
 | |
|                               "test_spawn_throw_stack"],
 | |
|                              ex);
 | |
|     run_next_test();
 | |
|   });
 | |
| });
 | |
| 
 | |
| // Test that we get an acceptable rewritten stack when we yield
 | |
| // a rejection in a Task.spawn.
 | |
| add_test(function test_spawn_yield_reject_stack() {
 | |
|   Task.spawn(function* task_spawn_yield_reject_stack() {
 | |
|     for (let i = 0; i < 5; ++i) {
 | |
|       yield Promise.resolve(); // Without stack rewrite, this would lose valuable information
 | |
|     }
 | |
|     yield Promise.reject(new Error("BOOM"));
 | |
|   }).then(do_throw, function(ex) {
 | |
|     do_check_rewritten_stack(["task_spawn_yield_reject_stack",
 | |
|                               "test_spawn_yield_reject_stack"],
 | |
|                               ex);
 | |
|     run_next_test();
 | |
|   });
 | |
| });
 | |
| 
 | |
| // Test that we get an acceptable rewritten stack when we launch
 | |
| // an error in a Task.async function.
 | |
| add_test(function test_async_function_throw_stack() {
 | |
|   let task_async_function_throw_stack = Task.async(function*() {
 | |
|     for (let i = 0; i < 5; ++i) {
 | |
|       yield Promise.resolve(); // Without stack rewrite, this would lose valuable information
 | |
|     }
 | |
|     throw new Error("BOOM");
 | |
|   })().then(do_throw, function(ex) {
 | |
|     do_check_rewritten_stack(["task_async_function_throw_stack",
 | |
|                               "test_async_function_throw_stack"],
 | |
|                              ex);
 | |
|     run_next_test();
 | |
|   });
 | |
| });
 | |
| 
 | |
| // Test that we get an acceptable rewritten stack when we launch
 | |
| // an error in a Task.async function.
 | |
| add_test(function test_async_function_yield_reject_stack() {
 | |
|   let task_async_function_yield_reject_stack = Task.async(function*() {
 | |
|     for (let i = 0; i < 5; ++i) {
 | |
|       yield Promise.resolve(); // Without stack rewrite, this would lose valuable information
 | |
|     }
 | |
|     yield Promise.reject(new Error("BOOM"));
 | |
|   })().then(do_throw, function(ex) {
 | |
|     do_check_rewritten_stack(["task_async_function_yield_reject_stack",
 | |
|                               "test_async_function_yield_reject_stack"],
 | |
|                               ex);
 | |
|     run_next_test();
 | |
|   });
 | |
| });
 | |
| 
 | |
| // Test that we get an acceptable rewritten stack when we launch
 | |
| // an error in a Task.async function.
 | |
| add_test(function test_async_method_throw_stack() {
 | |
|   let object = {
 | |
|    task_async_method_throw_stack: Task.async(function*() {
 | |
|     for (let i = 0; i < 5; ++i) {
 | |
|       yield Promise.resolve(); // Without stack rewrite, this would lose valuable information
 | |
|     }
 | |
|     throw new Error("BOOM");
 | |
|    })
 | |
|   };
 | |
|   object.task_async_method_throw_stack().then(do_throw, function(ex) {
 | |
|     do_check_rewritten_stack(["task_async_method_throw_stack",
 | |
|                               "test_async_method_throw_stack"],
 | |
|                              ex);
 | |
|     run_next_test();
 | |
|   });
 | |
| });
 | |
| 
 | |
| // Test that we get an acceptable rewritten stack when we launch
 | |
| // an error in a Task.async function.
 | |
| add_test(function test_async_method_yield_reject_stack() {
 | |
|   let object = {
 | |
|     task_async_method_yield_reject_stack: Task.async(function*() {
 | |
|       for (let i = 0; i < 5; ++i) {
 | |
|         yield Promise.resolve(); // Without stack rewrite, this would lose valuable information
 | |
|       }
 | |
|       yield Promise.reject(new Error("BOOM"));
 | |
|     })
 | |
|   };
 | |
|   object.task_async_method_yield_reject_stack().then(do_throw, function(ex) {
 | |
|     do_check_rewritten_stack(["task_async_method_yield_reject_stack",
 | |
|                               "test_async_method_yield_reject_stack"],
 | |
|                               ex);
 | |
|     run_next_test();
 | |
|   });
 | |
| });
 | |
| 
 | |
| // Test that two tasks whose execution takes place interleaved do not capture each other's stack.
 | |
| add_test(function test_throw_stack_do_not_capture_the_wrong_task() {
 | |
|   for (let iter_a of [3, 4, 5]) { // Vary the interleaving
 | |
|     for (let iter_b of [3, 4, 5]) {
 | |
|       Task.spawn(function* task_a() {
 | |
|         for (let i = 0; i < iter_a; ++i) {
 | |
|           yield Promise.resolve();
 | |
|         }
 | |
|         throw new Error("BOOM");
 | |
|       }).then(do_throw, function(ex) {
 | |
|         do_check_rewritten_stack(["task_a",
 | |
|                                   "test_throw_stack_do_not_capture_the_wrong_task"],
 | |
|                                   ex);
 | |
|         Assert.ok(!ex.stack.includes("task_b"));
 | |
|         run_next_test();
 | |
|       });
 | |
|       Task.spawn(function* task_b() {
 | |
|         for (let i = 0; i < iter_b; ++i) {
 | |
|           yield Promise.resolve();
 | |
|         }
 | |
|       });
 | |
|     }
 | |
|   }
 | |
| });
 | |
| 
 | |
| // Put things together
 | |
| add_test(function test_throw_complex_stack()
 | |
| {
 | |
|   // Setup the following stack:
 | |
|   //    inner_method()
 | |
|   //    task_3()
 | |
|   //    task_2()
 | |
|   //    task_1()
 | |
|   //    function_3()
 | |
|   //    function_2()
 | |
|   //    function_1()
 | |
|   //    test_throw_complex_stack()
 | |
|   (function function_1() {
 | |
|     return (function function_2() {
 | |
|       return (function function_3() {
 | |
|         return Task.spawn(function* task_1() {
 | |
|           yield Promise.resolve();
 | |
|           try {
 | |
|             yield Task.spawn(function* task_2() {
 | |
|               yield Promise.resolve();
 | |
|               yield Task.spawn(function* task_3() {
 | |
|                 yield Promise.resolve();
 | |
|                   let inner_object = {
 | |
|                     inner_method: Task.async(function*() {
 | |
|                       throw new Error("BOOM");
 | |
|                     })
 | |
|                   };
 | |
|                   yield Promise.resolve();
 | |
|                   yield inner_object.inner_method();
 | |
|                 });
 | |
|               });
 | |
|             } catch (ex) {
 | |
|               yield Promise.resolve();
 | |
|               throw ex;
 | |
|             }
 | |
|           });
 | |
|         })();
 | |
|       })();
 | |
|   })().then(
 | |
|     () => do_throw("Shouldn't have succeeded"),
 | |
|     (ex) => {
 | |
|       let expect = ["inner_method",
 | |
|         "task_3",
 | |
|         "task_2",
 | |
|         "task_1",
 | |
|         "function_3",
 | |
|         "function_2",
 | |
|         "function_1",
 | |
|         "test_throw_complex_stack"];
 | |
|       do_check_rewritten_stack(expect, ex);
 | |
| 
 | |
|       run_next_test();
 | |
|     });
 | |
| });
 | |
| 
 | |
| add_test(function test_without_maintainStack() {
 | |
|   info("Calling generateReadableStack without a Task");
 | |
|   Task.Debugging.generateReadableStack(new Error("Not a real error"));
 | |
| 
 | |
|   Task.Debugging.maintainStack = false;
 | |
| 
 | |
|   info("Calling generateReadableStack with neither a Task nor maintainStack");
 | |
|   Task.Debugging.generateReadableStack(new Error("Not a real error"));
 | |
| 
 | |
|   info("Calling generateReadableStack without maintainStack");
 | |
|   Task.spawn(function*() {
 | |
|     Task.Debugging.generateReadableStack(new Error("Not a real error"));
 | |
|     run_next_test();
 | |
|   });
 | |
| });
 | |
| 
 | |
| add_test(function exit_stack_tests() {
 | |
|   Task.Debugging.maintainStack = maintainStack;
 | |
|   run_next_test();
 | |
| });
 | |
| 
 |