Browse Source

Non-LIFO return error reified.

Amber cannot do non-LIFO non-local returns.
Herby Vojčík 3 years ago
parent
commit
04c7fa7508

+ 1 - 0
CHANGELOG

@@ -2,6 +2,7 @@
 ===================================
 ===================================
 
 
 * A model in `Promise new: [ :model | ... ]` reified as PromiseExecution.
 * A model in `Promise new: [ :model | ... ]` reified as PromiseExecution.
+* Non-LIFO return represented by own Error subclass NonLifoReturn.
 
 
 Commits: https://lolg.it/amber/amber/commits/0.29.6.
 Commits: https://lolg.it/amber/amber/commits/0.29.6.
 
 

+ 1 - 0
lang/API-CHANGES.txt

@@ -2,6 +2,7 @@
 
 
 * Add TPromiseModel with unary value / signal passing to 1-arg.
 * Add TPromiseModel with unary value / signal passing to 1-arg.
   * Promise class as well as PromiseExecution use it.
   * Promise class as well as PromiseExecution use it.
+* Add class NonLifoReturn.
 
 
 + Error class >>
 + Error class >>
   + messageText:
   + messageText:

+ 98 - 0
lang/src/Kernel-Exceptions.js

@@ -881,4 +881,102 @@ return $recv($1)._signal();
 }; }),
 }; }),
 $globals.NonBooleanReceiver.a$cls);
 $globals.NonBooleanReceiver.a$cls);
 
 
+
+$core.addClass("NonLifoReturn", $globals.Error, "Kernel-Exceptions");
+$core.setSlots($globals.NonLifoReturn, ["value"]);
+$core.addMethod(
+$core.method({
+selector: "messageText",
+protocol: "accessing",
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "messageText\x0a\x09^ 'Non-LIFO return: ', self value asString",
+referencedClasses: [],
+//>>excludeEnd("ide");
+pragmas: [],
+messageSends: [",", "asString", "value"]
+}, function ($methodClass){ return function (){
+var self=this,$self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+return "Non-LIFO return: ".__comma($recv($self._value())._asString());
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"messageText",{})});
+//>>excludeEnd("ctx");
+}; }),
+$globals.NonLifoReturn);
+
+$core.addMethod(
+$core.method({
+selector: "value",
+protocol: "accessing",
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "value\x0a\x09^ value",
+referencedClasses: [],
+//>>excludeEnd("ide");
+pragmas: [],
+messageSends: []
+}, function ($methodClass){ return function (){
+var self=this,$self=this;
+return $self.value;
+
+}; }),
+$globals.NonLifoReturn);
+
+$core.addMethod(
+$core.method({
+selector: "value:",
+protocol: "accessing",
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["anObject"],
+source: "value: anObject\x0a\x09value := anObject",
+referencedClasses: [],
+//>>excludeEnd("ide");
+pragmas: [],
+messageSends: []
+}, function ($methodClass){ return function (anObject){
+var self=this,$self=this;
+$self.value=anObject;
+return self;
+
+}; }),
+$globals.NonLifoReturn);
+
+
+$core.addMethod(
+$core.method({
+selector: "value:",
+protocol: "instance creation",
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["anObject"],
+source: "value: anObject\x0a\x09^ super new\x0a\x09\x09value: anObject;\x0a\x09\x09yourself",
+referencedClasses: [],
+//>>excludeEnd("ide");
+pragmas: [],
+messageSends: ["value:", "new", "yourself"]
+}, function ($methodClass){ return function (anObject){
+var self=this,$self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $1;
+$1=[(
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.supercall = true,
+//>>excludeEnd("ctx");
+($methodClass.superclass||$boot.nilAsClass).fn.prototype._new.call($self))
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+,$ctx1.supercall = false
+//>>excludeEnd("ctx");
+][0];
+$recv($1)._value_(anObject);
+return $recv($1)._yourself();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"value:",{anObject:anObject})});
+//>>excludeEnd("ctx");
+}; }),
+$globals.NonLifoReturn.a$cls);
+
 });
 });

+ 26 - 0
lang/src/Kernel-Exceptions.st

@@ -242,3 +242,29 @@ signalOn: anObject
 		signal
 		signal
 ! !
 ! !
 
 
+Error subclass: #NonLifoReturn
+	slots: {#value}
+	package: 'Kernel-Exceptions'!
+
+!NonLifoReturn methodsFor: 'accessing'!
+
+messageText
+	^ 'Non-LIFO return: ', self value asString
+!
+
+value
+	^ value
+!
+
+value: anObject
+	value := anObject
+! !
+
+!NonLifoReturn class methodsFor: 'instance creation'!
+
+value: anObject
+	^ super new
+		value: anObject;
+		yourself
+! !
+

+ 31 - 3
lang/src/Kernel-Infrastructure.js

@@ -3087,11 +3087,11 @@ selector: "asSmalltalkException:",
 protocol: "error handling",
 protocol: "error handling",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["anObject"],
 args: ["anObject"],
-source: "asSmalltalkException: anObject\x0a\x09\x22A JavaScript exception may be thrown.\x0a\x09We then need to convert it back to a Smalltalk object\x22\x0a\x09\x0a\x09^ anObject\x0a\x09\x09ifNil: [ [ self error: 'Error: nil' ] on: Error do: [ :e | e ] ]\x0a\x09\x09ifNotNil: [\x0a\x09\x09\x09(self isError: anObject)\x0a\x09\x09\x09\x09ifTrue: [ anObject ]\x0a\x09\x09\x09\x09ifFalse: [ JavaScriptException on: anObject ] ]",
-referencedClasses: ["Error", "JavaScriptException"],
+source: "asSmalltalkException: anObject\x0a\x09\x22A JavaScript exception may be thrown.\x0a\x09We then need to convert it back to a Smalltalk object\x22\x0a\x09\x0a\x09^ anObject\x0a\x09\x09ifNil: [ [ self error: 'Error: nil' ] on: Error do: [ :e | e ] ]\x0a\x09\x09ifNotNil: [\x0a\x09\x09\x09(self isError: anObject)\x0a\x09\x09\x09\x09ifTrue: [ anObject ]\x0a\x09\x09\x09\x09ifFalse: [\x0a\x09\x09\x09\x09\x09(self isNonLocalReturn: anObject)\x0a\x09\x09\x09\x09\x09\x09ifTrue: [ NonLifoReturn value: anObject first ]\x0a\x09\x09\x09\x09\x09\x09ifFalse: [ JavaScriptException on: anObject ] ] ]",
+referencedClasses: ["Error", "NonLifoReturn", "JavaScriptException"],
 //>>excludeEnd("ide");
 //>>excludeEnd("ide");
 pragmas: [],
 pragmas: [],
-messageSends: ["ifNil:ifNotNil:", "on:do:", "error:", "ifTrue:ifFalse:", "isError:", "on:"]
+messageSends: ["ifNil:ifNotNil:", "on:do:", "error:", "ifTrue:ifFalse:", "isError:", "isNonLocalReturn:", "value:", "first", "on:"]
 }, function ($methodClass){ return function (anObject){
 }, function ($methodClass){ return function (anObject){
 var self=this,$self=this;
 var self=this,$self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
@@ -3114,9 +3114,13 @@ return e;
 if($core.assert($self._isError_(anObject))){
 if($core.assert($self._isError_(anObject))){
 return anObject;
 return anObject;
 } else {
 } else {
+if($core.assert($self._isNonLocalReturn_(anObject))){
+return $recv($globals.NonLifoReturn)._value_($recv(anObject)._first());
+} else {
 return $recv($globals.JavaScriptException)._on_(anObject);
 return $recv($globals.JavaScriptException)._on_(anObject);
 }
 }
 }
 }
+}
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 }, function($ctx1) {$ctx1.fill(self,"asSmalltalkException:",{anObject:anObject})});
 }, function($ctx1) {$ctx1.fill(self,"asSmalltalkException:",{anObject:anObject})});
 //>>excludeEnd("ctx");
 //>>excludeEnd("ctx");
@@ -3481,6 +3485,30 @@ return false;
 }; }),
 }; }),
 $globals.SmalltalkImage);
 $globals.SmalltalkImage);
 
 
+$core.addMethod(
+$core.method({
+selector: "isNonLocalReturn:",
+protocol: "testing",
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["anObject"],
+source: "isNonLocalReturn: anObject\x0a\x09<inlineJS: 'return Array.isArray(anObject) && anObject.length === 1'>",
+referencedClasses: [],
+//>>excludeEnd("ide");
+pragmas: [["inlineJS:", ["return Array.isArray(anObject) && anObject.length === 1"]]],
+messageSends: []
+}, function ($methodClass){ return function (anObject){
+var self=this,$self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+return Array.isArray(anObject) && anObject.length === 1;
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"isNonLocalReturn:",{anObject:anObject})});
+//>>excludeEnd("ctx");
+}; }),
+$globals.SmalltalkImage);
+
 $core.addMethod(
 $core.addMethod(
 $core.method({
 $core.method({
 selector: "isSmalltalkObject:",
 selector: "isSmalltalkObject:",

+ 8 - 1
lang/src/Kernel-Infrastructure.st

@@ -942,7 +942,10 @@ asSmalltalkException: anObject
 		ifNotNil: [
 		ifNotNil: [
 			(self isError: anObject)
 			(self isError: anObject)
 				ifTrue: [ anObject ]
 				ifTrue: [ anObject ]
-				ifFalse: [ JavaScriptException on: anObject ] ]
+				ifFalse: [
+					(self isNonLocalReturn: anObject)
+						ifTrue: [ NonLifoReturn value: anObject first ]
+						ifFalse: [ JavaScriptException on: anObject ] ] ]
 !
 !
 
 
 try: actionBlock ifTrue: aBlock catch: anotherBlock
 try: actionBlock ifTrue: aBlock catch: anotherBlock
@@ -1107,6 +1110,10 @@ isError: anObject
 	^ (self isSmalltalkObject: anObject) and: [ anObject isError ]
 	^ (self isSmalltalkObject: anObject) and: [ anObject isError ]
 !
 !
 
 
+isNonLocalReturn: anObject
+	<inlineJS: 'return Array.isArray(anObject) && anObject.length === 1'>
+!
+
 isSmalltalkObject: anObject
 isSmalltalkObject: anObject
 	"Consider anObject a Smalltalk object if it has a 'a$cls' property.
 	"Consider anObject a Smalltalk object if it has a 'a$cls' property.
 	Note that this may be unaccurate"
 	Note that this may be unaccurate"

+ 13 - 11
lang/src/Kernel-Promises.js

@@ -145,10 +145,10 @@ selector: "new:",
 protocol: "instance creation",
 protocol: "instance creation",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["aBlock"],
 args: ["aBlock"],
-source: "new: aBlock\x0a\x22Returns a Promise that is eventually resolved or rejected.\x0aPass a block that is called with one argument, model.\x0aYou should call model value: ... to resolve the promise\x0aand model signal: ... to reject the promise.\x0aIf error happens during run of the block,\x0apromise is rejected with that error as well.\x22\x0a<inlineJS: '\x0a\x09var localReturn = null,\x0a\x09\x09promise = new Promise(function (resolve, reject) {\x0a\x09\x09    var model = $globals.PromiseExecution._resolveBlock_rejectBlock_(resolve, reject);\x0a\x09\x09    try { aBlock._value_(model); }\x0a\x09\x09\x09catch (ex) {\x0a\x09\x09\x09\x09if (Array.isArray(ex) && ex.length === 1) localReturn = ex;\x0a\x09\x09\x09\x09else reject(ex);\x0a\x09\x09\x09}\x0a\x09\x09});\x0a\x09if (localReturn) throw localReturn; else return promise;\x0a'>",
+source: "new: aBlock\x0a\x22Returns a Promise that is eventually resolved or rejected.\x0aPass a block that is called with one argument, model.\x0aYou should call model value: ... to resolve the promise\x0aand model signal: ... to reject the promise.\x0aIf error happens during run of the block,\x0apromise is rejected with that error as well.\x22\x0a<inlineJS: '\x0a\x09var nonLocalReturn = null,\x0a\x09\x09promise = new Promise(function (resolve, reject) {\x0a\x09\x09    var model = $globals.PromiseExecution._resolveBlock_rejectBlock_(resolve, reject);\x0a\x09\x09    try { aBlock._value_(model); }\x0a\x09\x09\x09catch (ex) {\x0a\x09\x09\x09\x09if (Array.isArray(ex) && ex.length === 1) nonLocalReturn = ex;\x0a\x09\x09\x09\x09else reject(ex);\x0a\x09\x09\x09}\x0a\x09\x09});\x0a\x09if (nonLocalReturn) throw nonLocalReturn; else return promise;\x0a'>",
 referencedClasses: [],
 referencedClasses: [],
 //>>excludeEnd("ide");
 //>>excludeEnd("ide");
-pragmas: [["inlineJS:", ["\x0a\x09var localReturn = null,\x0a\x09\x09promise = new Promise(function (resolve, reject) {\x0a\x09\x09    var model = $globals.PromiseExecution._resolveBlock_rejectBlock_(resolve, reject);\x0a\x09\x09    try { aBlock._value_(model); }\x0a\x09\x09\x09catch (ex) {\x0a\x09\x09\x09\x09if (Array.isArray(ex) && ex.length === 1) localReturn = ex;\x0a\x09\x09\x09\x09else reject(ex);\x0a\x09\x09\x09}\x0a\x09\x09});\x0a\x09if (localReturn) throw localReturn; else return promise;"]]],
+pragmas: [["inlineJS:", ["\x0a\x09var nonLocalReturn = null,\x0a\x09\x09promise = new Promise(function (resolve, reject) {\x0a\x09\x09    var model = $globals.PromiseExecution._resolveBlock_rejectBlock_(resolve, reject);\x0a\x09\x09    try { aBlock._value_(model); }\x0a\x09\x09\x09catch (ex) {\x0a\x09\x09\x09\x09if (Array.isArray(ex) && ex.length === 1) nonLocalReturn = ex;\x0a\x09\x09\x09\x09else reject(ex);\x0a\x09\x09\x09}\x0a\x09\x09});\x0a\x09if (nonLocalReturn) throw nonLocalReturn; else return promise;"]]],
 messageSends: []
 messageSends: []
 }, function ($methodClass){ return function (aBlock){
 }, function ($methodClass){ return function (aBlock){
 var self=this,$self=this;
 var self=this,$self=this;
@@ -156,16 +156,16 @@ var self=this,$self=this;
 return $core.withContext(function($ctx1) {
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
 //>>excludeEnd("ctx");
 
 
-	var localReturn = null,
+	var nonLocalReturn = null,
 		promise = new Promise(function (resolve, reject) {
 		promise = new Promise(function (resolve, reject) {
 		    var model = $globals.PromiseExecution._resolveBlock_rejectBlock_(resolve, reject);
 		    var model = $globals.PromiseExecution._resolveBlock_rejectBlock_(resolve, reject);
 		    try { aBlock._value_(model); }
 		    try { aBlock._value_(model); }
 			catch (ex) {
 			catch (ex) {
-				if (Array.isArray(ex) && ex.length === 1) localReturn = ex;
+				if (Array.isArray(ex) && ex.length === 1) nonLocalReturn = ex;
 				else reject(ex);
 				else reject(ex);
 			}
 			}
 		});
 		});
-	if (localReturn) throw localReturn; else return promise;;
+	if (nonLocalReturn) throw nonLocalReturn; else return promise;;
 return self;
 return self;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 }, function($ctx1) {$ctx1.fill(self,"new:",{aBlock:aBlock})});
 }, function($ctx1) {$ctx1.fill(self,"new:",{aBlock:aBlock})});
@@ -230,7 +230,7 @@ selector: "do:",
 protocol: "evaluating",
 protocol: "evaluating",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["aBlock"],
 args: ["aBlock"],
-source: "do: aBlock\x0a\x09self value: (self try: aBlock)",
+source: "do: aBlock\x0a\x09\x22Executes a block 'in the context of a promise' and resolves.\x0a\x09That is, if it ends with an error, promise is rejected.\x0a\x09If a block succeeds, promise is resolved with its return value.\x0a\x09Non-local returns are also treated as an error and reified as rejections.\x22\x0a\x09self value: (self try: aBlock)",
 referencedClasses: [],
 referencedClasses: [],
 //>>excludeEnd("ide");
 //>>excludeEnd("ide");
 pragmas: [],
 pragmas: [],
@@ -298,10 +298,10 @@ selector: "try:",
 protocol: "evaluating",
 protocol: "evaluating",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["aBlock"],
 args: ["aBlock"],
-source: "try: aBlock\x0a\x09<inlineJS: '\x0a\x09\x09try {\x0a\x09\x09\x09return aBlock._value();\x0a\x09\x09} catch(error) {\x0a\x09\x09\x09// pass non-local returns undetected\x0a\x09\x09\x09if (Array.isArray(error) && error.length === 1) throw error;\x0a\x09\x09\x09self._signal_(error);\x0a\x09\x09}\x0a\x09'>",
+source: "try: aBlock\x0a\x09\x22Executes a block 'in the context of a promise'.\x0a\x09That is, if it ends with an error, promise is rejected.\x0a\x09Non-local returns are also treated as an error and reified as rejections.\x22\x0a\x09<inlineJS: '\x0a\x09\x09try {\x0a\x09\x09\x09return aBlock._value();\x0a\x09\x09} catch(error) {\x0a\x09\x09\x09$self._signal_(\x0a\x09\x09\x09\x09Array.isArray(error) && error.length === 1 ?\x0a\x09\x09\x09\x09\x09$globals.NonLifoReturn._value_(error[0]) :\x0a\x09\x09\x09\x09\x09error\x0a\x09\x09\x09);\x0a\x09\x09}\x0a\x09'>",
 referencedClasses: [],
 referencedClasses: [],
 //>>excludeEnd("ide");
 //>>excludeEnd("ide");
-pragmas: [["inlineJS:", ["\x0a\x09\x09try {\x0a\x09\x09\x09return aBlock._value();\x0a\x09\x09} catch(error) {\x0a\x09\x09\x09// pass non-local returns undetected\x0a\x09\x09\x09if (Array.isArray(error) && error.length === 1) throw error;\x0a\x09\x09\x09self._signal_(error);\x0a\x09\x09}\x0a\x09"]]],
+pragmas: [["inlineJS:", ["\x0a\x09\x09try {\x0a\x09\x09\x09return aBlock._value();\x0a\x09\x09} catch(error) {\x0a\x09\x09\x09$self._signal_(\x0a\x09\x09\x09\x09Array.isArray(error) && error.length === 1 ?\x0a\x09\x09\x09\x09\x09$globals.NonLifoReturn._value_(error[0]) :\x0a\x09\x09\x09\x09\x09error\x0a\x09\x09\x09);\x0a\x09\x09}\x0a\x09"]]],
 messageSends: []
 messageSends: []
 }, function ($methodClass){ return function (aBlock){
 }, function ($methodClass){ return function (aBlock){
 var self=this,$self=this;
 var self=this,$self=this;
@@ -312,9 +312,11 @@ return $core.withContext(function($ctx1) {
 		try {
 		try {
 			return aBlock._value();
 			return aBlock._value();
 		} catch(error) {
 		} catch(error) {
-			// pass non-local returns undetected
-			if (Array.isArray(error) && error.length === 1) throw error;
-			self._signal_(error);
+			$self._signal_(
+				Array.isArray(error) && error.length === 1 ?
+					$globals.NonLifoReturn._value_(error[0]) :
+					error
+			);
 		}
 		}
 	;
 	;
 return self;
 return self;

+ 15 - 6
lang/src/Kernel-Promises.st

@@ -40,16 +40,16 @@ and model signal: ... to reject the promise.
 If error happens during run of the block,
 If error happens during run of the block,
 promise is rejected with that error as well."
 promise is rejected with that error as well."
 <inlineJS: '
 <inlineJS: '
-	var localReturn = null,
+	var nonLocalReturn = null,
 		promise = new Promise(function (resolve, reject) {
 		promise = new Promise(function (resolve, reject) {
 		    var model = $globals.PromiseExecution._resolveBlock_rejectBlock_(resolve, reject);
 		    var model = $globals.PromiseExecution._resolveBlock_rejectBlock_(resolve, reject);
 		    try { aBlock._value_(model); }
 		    try { aBlock._value_(model); }
 			catch (ex) {
 			catch (ex) {
-				if (Array.isArray(ex) && ex.length === 1) localReturn = ex;
+				if (Array.isArray(ex) && ex.length === 1) nonLocalReturn = ex;
 				else reject(ex);
 				else reject(ex);
 			}
 			}
 		});
 		});
-	if (localReturn) throw localReturn; else return promise;
+	if (nonLocalReturn) throw nonLocalReturn; else return promise;
 '>
 '>
 !
 !
 
 
@@ -77,17 +77,26 @@ resolveBlock: aBlock rejectBlock: anotherBlock
 !PromiseExecution methodsFor: 'evaluating'!
 !PromiseExecution methodsFor: 'evaluating'!
 
 
 do: aBlock
 do: aBlock
+	"Executes a block 'in the context of a promise' and resolves.
+	That is, if it ends with an error, promise is rejected.
+	If a block succeeds, promise is resolved with its return value.
+	Non-local returns are also treated as an error and reified as rejections."
 	self value: (self try: aBlock)
 	self value: (self try: aBlock)
 !
 !
 
 
 try: aBlock
 try: aBlock
+	"Executes a block 'in the context of a promise'.
+	That is, if it ends with an error, promise is rejected.
+	Non-local returns are also treated as an error and reified as rejections."
 	<inlineJS: '
 	<inlineJS: '
 		try {
 		try {
 			return aBlock._value();
 			return aBlock._value();
 		} catch(error) {
 		} catch(error) {
-			// pass non-local returns undetected
-			if (Array.isArray(error) && error.length === 1) throw error;
-			self._signal_(error);
+			$self._signal_(
+				Array.isArray(error) && error.length === 1 ?
+					$globals.NonLifoReturn._value_(error[0]) :
+					error
+			);
 		}
 		}
 	'>
 	'>
 ! !
 ! !

+ 236 - 0
lang/src/Kernel-Tests.js

@@ -15672,6 +15672,69 @@ $globals.PointTest);
 
 
 
 
 $core.addClass("PromiseTest", $globals.TestCase, "Kernel-Tests");
 $core.addClass("PromiseTest", $globals.TestCase, "Kernel-Tests");
+$core.addMethod(
+$core.method({
+selector: "testPromiseExecutorAsyncDoWithNonLocalReturn",
+protocol: " tests",
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "testPromiseExecutorAsyncDoWithNonLocalReturn\x0a\x09self timeout: 40.\x0a\x09^ (Promise new: [ :m | [ m do: [ ^  'Intentional' ] ] fork ])\x0a\x09\x09then: [ self assert: false description: 'Should not have been resolved' ]\x0a\x09\x09on: NonLifoReturn do: [ :nonlifo | self assert: nonlifo value equals: 'Intentional' ]",
+referencedClasses: ["Promise", "NonLifoReturn"],
+//>>excludeEnd("ide");
+pragmas: [],
+messageSends: ["timeout:", "then:on:do:", "new:", "fork", "do:", "assert:description:", "assert:equals:", "value"]
+}, function ($methodClass){ return function (){
+var self=this,$self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $early={};
+try {
+$self._timeout_((40));
+return $recv($recv($globals.Promise)._new_((function(m){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+return $recv((function(){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx3) {
+//>>excludeEnd("ctx");
+return $recv(m)._do_((function(){
+throw $early=["Intentional"];
+
+}));
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx3) {$ctx3.fillBlock({},$ctx2,2)});
+//>>excludeEnd("ctx");
+}))._fork();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({m:m},$ctx1,1)});
+//>>excludeEnd("ctx");
+})))._then_on_do_((function(){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+return $self._assert_description_(false,"Should not have been resolved");
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,4)});
+//>>excludeEnd("ctx");
+}),$globals.NonLifoReturn,(function(nonlifo){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+return $self._assert_equals_($recv(nonlifo)._value(),"Intentional");
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({nonlifo:nonlifo},$ctx1,5)});
+//>>excludeEnd("ctx");
+}));
+}
+catch(e) {if(e===$early)return e[0]; throw e}
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"testPromiseExecutorAsyncDoWithNonLocalReturn",{})});
+//>>excludeEnd("ctx");
+}; }),
+$globals.PromiseTest);
+
 $core.addMethod(
 $core.addMethod(
 $core.method({
 $core.method({
 selector: "testPromiseExecutorAsyncNegativeDo",
 selector: "testPromiseExecutorAsyncNegativeDo",
@@ -15938,6 +16001,124 @@ return $self._assert_equals_(result,"timeout");
 }; }),
 }; }),
 $globals.PromiseTest);
 $globals.PromiseTest);
 
 
+$core.addMethod(
+$core.method({
+selector: "testPromiseExecutorAsyncTryWithNonLocalReturn",
+protocol: " tests",
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "testPromiseExecutorAsyncTryWithNonLocalReturn\x0a\x09self timeout: 40.\x0a\x09^ (Promise new: [ :m | [ m try: [ ^  'Intentional' ] ] fork ])\x0a\x09\x09then: [ self assert: false description: 'Should not have been resolved' ]\x0a\x09\x09on: NonLifoReturn do: [ :nonlifo | self assert: nonlifo value equals: 'Intentional' ]",
+referencedClasses: ["Promise", "NonLifoReturn"],
+//>>excludeEnd("ide");
+pragmas: [],
+messageSends: ["timeout:", "then:on:do:", "new:", "fork", "try:", "assert:description:", "assert:equals:", "value"]
+}, function ($methodClass){ return function (){
+var self=this,$self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $early={};
+try {
+$self._timeout_((40));
+return $recv($recv($globals.Promise)._new_((function(m){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+return $recv((function(){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx3) {
+//>>excludeEnd("ctx");
+return $recv(m)._try_((function(){
+throw $early=["Intentional"];
+
+}));
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx3) {$ctx3.fillBlock({},$ctx2,2)});
+//>>excludeEnd("ctx");
+}))._fork();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({m:m},$ctx1,1)});
+//>>excludeEnd("ctx");
+})))._then_on_do_((function(){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+return $self._assert_description_(false,"Should not have been resolved");
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,4)});
+//>>excludeEnd("ctx");
+}),$globals.NonLifoReturn,(function(nonlifo){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+return $self._assert_equals_($recv(nonlifo)._value(),"Intentional");
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({nonlifo:nonlifo},$ctx1,5)});
+//>>excludeEnd("ctx");
+}));
+}
+catch(e) {if(e===$early)return e[0]; throw e}
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"testPromiseExecutorAsyncTryWithNonLocalReturn",{})});
+//>>excludeEnd("ctx");
+}; }),
+$globals.PromiseTest);
+
+$core.addMethod(
+$core.method({
+selector: "testPromiseExecutorDoWithNonLocalReturn",
+protocol: " tests",
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "testPromiseExecutorDoWithNonLocalReturn\x0a\x09self timeout: 20.\x0a\x09^ (Promise new: [ :m | m do: [ ^ 'Intentional' ] ])\x0a\x09\x09then: [ self assert: false description: 'Should not have been resolved' ]\x0a\x09\x09on: NonLifoReturn do: [ :nonlifo | self assert: nonlifo value equals: 'Intentional' ]",
+referencedClasses: ["Promise", "NonLifoReturn"],
+//>>excludeEnd("ide");
+pragmas: [],
+messageSends: ["timeout:", "then:on:do:", "new:", "do:", "assert:description:", "assert:equals:", "value"]
+}, function ($methodClass){ return function (){
+var self=this,$self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $early={};
+try {
+$self._timeout_((20));
+return $recv($recv($globals.Promise)._new_((function(m){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+return $recv(m)._do_((function(){
+throw $early=["Intentional"];
+
+}));
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({m:m},$ctx1,1)});
+//>>excludeEnd("ctx");
+})))._then_on_do_((function(){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+return $self._assert_description_(false,"Should not have been resolved");
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,3)});
+//>>excludeEnd("ctx");
+}),$globals.NonLifoReturn,(function(nonlifo){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+return $self._assert_equals_($recv(nonlifo)._value(),"Intentional");
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({nonlifo:nonlifo},$ctx1,4)});
+//>>excludeEnd("ctx");
+}));
+}
+catch(e) {if(e===$early)return e[0]; throw e}
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"testPromiseExecutorDoWithNonLocalReturn",{})});
+//>>excludeEnd("ctx");
+}; }),
+$globals.PromiseTest);
+
 $core.addMethod(
 $core.addMethod(
 $core.method({
 $core.method({
 selector: "testPromiseExecutorNegativeDo",
 selector: "testPromiseExecutorNegativeDo",
@@ -16180,6 +16361,61 @@ return $self._assert_equals_(result,"timeout");
 }; }),
 }; }),
 $globals.PromiseTest);
 $globals.PromiseTest);
 
 
+$core.addMethod(
+$core.method({
+selector: "testPromiseExecutorTryWithNonLocalReturn",
+protocol: " tests",
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "testPromiseExecutorTryWithNonLocalReturn\x0a\x09self timeout: 20.\x0a\x09^ (Promise new: [ :m | m try: [ ^ 'Intentional' ] ])\x0a\x09\x09then: [ self assert: false description: 'Should not have been resolved' ]\x0a\x09\x09on: NonLifoReturn do: [ :nonlifo | self assert: nonlifo value equals: 'Intentional' ]",
+referencedClasses: ["Promise", "NonLifoReturn"],
+//>>excludeEnd("ide");
+pragmas: [],
+messageSends: ["timeout:", "then:on:do:", "new:", "try:", "assert:description:", "assert:equals:", "value"]
+}, function ($methodClass){ return function (){
+var self=this,$self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $early={};
+try {
+$self._timeout_((20));
+return $recv($recv($globals.Promise)._new_((function(m){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+return $recv(m)._try_((function(){
+throw $early=["Intentional"];
+
+}));
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({m:m},$ctx1,1)});
+//>>excludeEnd("ctx");
+})))._then_on_do_((function(){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+return $self._assert_description_(false,"Should not have been resolved");
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,3)});
+//>>excludeEnd("ctx");
+}),$globals.NonLifoReturn,(function(nonlifo){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+return $self._assert_equals_($recv(nonlifo)._value(),"Intentional");
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({nonlifo:nonlifo},$ctx1,4)});
+//>>excludeEnd("ctx");
+}));
+}
+catch(e) {if(e===$early)return e[0]; throw e}
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"testPromiseExecutorTryWithNonLocalReturn",{})});
+//>>excludeEnd("ctx");
+}; }),
+$globals.PromiseTest);
+
 $core.addMethod(
 $core.addMethod(
 $core.method({
 $core.method({
 selector: "testPromiseNew",
 selector: "testPromiseNew",

+ 28 - 0
lang/src/Kernel-Tests.st

@@ -2786,6 +2786,13 @@ TestCase subclass: #PromiseTest
 
 
 !PromiseTest methodsFor: ' tests'!
 !PromiseTest methodsFor: ' tests'!
 
 
+testPromiseExecutorAsyncDoWithNonLocalReturn
+	self timeout: 40.
+	^ (Promise new: [ :m | [ m do: [ ^  'Intentional' ] ] fork ])
+		then: [ self assert: false description: 'Should not have been resolved' ]
+		on: NonLifoReturn do: [ :nonlifo | self assert: nonlifo value equals: 'Intentional' ]
+!
+
 testPromiseExecutorAsyncNegativeDo
 testPromiseExecutorAsyncNegativeDo
 	self timeout: 40.
 	self timeout: 40.
 	^ (Promise new: [ :m | [ m do: [ self error: 'Intentional' ] ] fork ])
 	^ (Promise new: [ :m | [ m do: [ self error: 'Intentional' ] ] fork ])
@@ -2815,6 +2822,20 @@ testPromiseExecutorAsyncPositiveTry
 	}) then: [ :result | self assert: result equals: #timeout ].
 	}) then: [ :result | self assert: result equals: #timeout ].
 !
 !
 
 
+testPromiseExecutorAsyncTryWithNonLocalReturn
+	self timeout: 40.
+	^ (Promise new: [ :m | [ m try: [ ^  'Intentional' ] ] fork ])
+		then: [ self assert: false description: 'Should not have been resolved' ]
+		on: NonLifoReturn do: [ :nonlifo | self assert: nonlifo value equals: 'Intentional' ]
+!
+
+testPromiseExecutorDoWithNonLocalReturn
+	self timeout: 20.
+	^ (Promise new: [ :m | m do: [ ^ 'Intentional' ] ])
+		then: [ self assert: false description: 'Should not have been resolved' ]
+		on: NonLifoReturn do: [ :nonlifo | self assert: nonlifo value equals: 'Intentional' ]
+!
+
 testPromiseExecutorNegativeDo
 testPromiseExecutorNegativeDo
 	self timeout: 40.
 	self timeout: 40.
 	^ (Promise new: [ :m | [ m do: [ self error: 'Intentional' ] ] fork ])
 	^ (Promise new: [ :m | [ m do: [ self error: 'Intentional' ] ] fork ])
@@ -2844,6 +2865,13 @@ testPromiseExecutorPositiveTry
 	}) then: [ :result | self assert: result equals: #timeout ].
 	}) then: [ :result | self assert: result equals: #timeout ].
 !
 !
 
 
+testPromiseExecutorTryWithNonLocalReturn
+	self timeout: 20.
+	^ (Promise new: [ :m | m try: [ ^ 'Intentional' ] ])
+		then: [ self assert: false description: 'Should not have been resolved' ]
+		on: NonLifoReturn do: [ :nonlifo | self assert: nonlifo value equals: 'Intentional' ]
+!
+
 testPromiseNew
 testPromiseNew
 	self timeout: 20.
 	self timeout: 20.
 	^ Promise new
 	^ Promise new

+ 257 - 33
lang/src/SUnit-Tests.js

@@ -450,6 +450,40 @@ return self;
 }; }),
 }; }),
 $globals.SUnitAsyncTest);
 $globals.SUnitAsyncTest);
 
 
+$core.addMethod(
+$core.method({
+selector: "fakeNonLifoReturn",
+protocol: "helpers",
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "fakeNonLifoReturn\x0a\x09flag := 'bad'.\x0a\x09self timeout: 30.\x0a\x09flag := (self async: [ flag := 'ok'. ^ 'non-lifo' ]) valueWithTimeout: 20",
+referencedClasses: [],
+//>>excludeEnd("ide");
+pragmas: [],
+messageSends: ["timeout:", "valueWithTimeout:", "async:"]
+}, function ($methodClass){ return function (){
+var self=this,$self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $early={};
+try {
+$self.flag="bad";
+$self._timeout_((30));
+$self.flag=$recv($self._async_((function(){
+$self.flag="ok";
+throw $early=["non-lifo"];
+
+})))._valueWithTimeout_((20));
+return self;
+}
+catch(e) {if(e===$early)return e[0]; throw e}
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"fakeNonLifoReturn",{})});
+//>>excludeEnd("ctx");
+}; }),
+$globals.SUnitAsyncTest);
+
 $core.addMethod(
 $core.addMethod(
 $core.method({
 $core.method({
 selector: "fakeTimeout",
 selector: "fakeTimeout",
@@ -773,6 +807,81 @@ return self;
 }; }),
 }; }),
 $globals.SUnitAsyncTest);
 $globals.SUnitAsyncTest);
 
 
+$core.addMethod(
+$core.method({
+selector: "testNonLifo",
+protocol: "tests",
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "testNonLifo\x0a\x09| suite runner result assertBlock |\x0a\x09suite := #(fakeNonLifoReturn testPass) collect: [ :each | self class selector: each ].\x0a\x09runner := TestSuiteRunner on: suite.\x0a\x09self timeout: 200.\x0a\x09result := runner result.\x0a\x09assertBlock := self async: [\x0a\x09\x09self assert: (self selectorSetOf: result errors) equals: #(fakeNonLifoReturn) asSet.\x0a\x09\x09self assert: (self selectorSetOf: result failures) equals: Set new.\x0a\x09\x09\x22TODO check that error is indeed a correct NonLifoReturn\x22\x0a\x09\x09self finished\x0a\x09].\x0a\x09runner announcer on: ResultAnnouncement do: [ :ann |\x0a\x09\x09(ann result == result and: [ result runs = result total ]) ifTrue: assertBlock ].\x0a\x09runner run",
+referencedClasses: ["TestSuiteRunner", "Set", "ResultAnnouncement"],
+//>>excludeEnd("ide");
+pragmas: [],
+messageSends: ["collect:", "selector:", "class", "on:", "timeout:", "result", "async:", "assert:equals:", "selectorSetOf:", "errors", "asSet", "failures", "new", "finished", "on:do:", "announcer", "ifTrue:", "and:", "==", "=", "runs", "total", "run"]
+}, function ($methodClass){ return function (){
+var self=this,$self=this;
+var suite,runner,result,assertBlock;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $1;
+suite=["fakeNonLifoReturn", "testPass"]._collect_((function(each){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+return $recv($self._class())._selector_(each);
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({each:each},$ctx1,1)});
+//>>excludeEnd("ctx");
+}));
+runner=$recv($globals.TestSuiteRunner)._on_(suite);
+$self._timeout_((200));
+result=[$recv(runner)._result()
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+,$ctx1.sendIdx["result"]=1
+//>>excludeEnd("ctx");
+][0];
+assertBlock=$self._async_((function(){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+[$self._assert_equals_([$self._selectorSetOf_($recv(result)._errors())
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+,$ctx2.sendIdx["selectorSetOf:"]=1
+//>>excludeEnd("ctx");
+][0],["fakeNonLifoReturn"]._asSet())
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+,$ctx2.sendIdx["assert:equals:"]=1
+//>>excludeEnd("ctx");
+][0];
+$self._assert_equals_($self._selectorSetOf_($recv(result)._failures()),$recv($globals.Set)._new());
+return $self._finished();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,2)});
+//>>excludeEnd("ctx");
+}));
+$recv($recv(runner)._announcer())._on_do_($globals.ResultAnnouncement,(function(ann){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+if($core.assert($recv($recv(ann)._result()).__eq_eq(result))){
+$1=$recv($recv(result)._runs()).__eq($recv(result)._total());
+} else {
+$1=false;
+}
+return $recv($1)._ifTrue_(assertBlock);
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({ann:ann},$ctx1,3)});
+//>>excludeEnd("ctx");
+}));
+$recv(runner)._run();
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"testNonLifo",{suite:suite,runner:runner,result:result,assertBlock:assertBlock})});
+//>>excludeEnd("ctx");
+}; }),
+$globals.SUnitAsyncTest);
+
 $core.addMethod(
 $core.addMethod(
 $core.method({
 $core.method({
 selector: "testPass",
 selector: "testPass",
@@ -1146,6 +1255,40 @@ return $recv($globals.Promise)._delayMilliseconds_((20));
 }; }),
 }; }),
 $globals.SUnitPromiseTest);
 $globals.SUnitPromiseTest);
 
 
+$core.addMethod(
+$core.method({
+selector: "fakeNonLifoReturn",
+protocol: "helpers",
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "fakeNonLifoReturn\x0a\x09flag := 'bad'.\x0a\x09self timeout: 30.\x0a\x09flag := Promise delayMilliseconds: 20.\x0a\x09^ flag then: [ flag := 'ok'. ^ 'non-lifo' ]",
+referencedClasses: ["Promise"],
+//>>excludeEnd("ide");
+pragmas: [],
+messageSends: ["timeout:", "delayMilliseconds:", "then:"]
+}, function ($methodClass){ return function (){
+var self=this,$self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $early={};
+try {
+$self.flag="bad";
+$self._timeout_((30));
+$self.flag=$recv($globals.Promise)._delayMilliseconds_((20));
+return $recv($self.flag)._then_((function(){
+$self.flag="ok";
+throw $early=["non-lifo"];
+
+}));
+}
+catch(e) {if(e===$early)return e[0]; throw e}
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"fakeNonLifoReturn",{})});
+//>>excludeEnd("ctx");
+}; }),
+$globals.SUnitPromiseTest);
+
 $core.addMethod(
 $core.addMethod(
 $core.method({
 $core.method({
 selector: "fakePromiseWithoutTimeout",
 selector: "fakePromiseWithoutTimeout",
@@ -1321,6 +1464,88 @@ return $recv($globals.Promise)._new();
 }; }),
 }; }),
 $globals.SUnitPromiseTest);
 $globals.SUnitPromiseTest);
 
 
+$core.addMethod(
+$core.method({
+selector: "testNonLifo",
+protocol: "tests",
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "testNonLifo\x0a\x09| suite runner result |\x0a\x09suite := #(fakeNonLifoReturn testPass) collect: [ :each | self class selector: each ].\x0a\x09runner := TestSuiteRunner on: suite.\x0a\x09self timeout: 200.\x0a\x09result := runner result.\x0a\x09^ Promise new: [ :model |\x0a\x09\x09runner announcer on: ResultAnnouncement do: [ :ann |\x0a\x09\x09\x09(ann result == result and: [ result runs = result total ]) ifTrue: [ model do: [\x0a\x09\x09\x09\x09self assert: (self selectorSetOf: result errors) equals: #(fakeNonLifoReturn) asSet.\x0a\x09\x09\x09\x09self assert: (self selectorSetOf: result failures) equals: Set new.\x0a\x09\x09\x09\x09\x22TODO check that error is indeed a correct NonLifoReturn\x22\x0a\x09\x09] ] ].\x0a\x09\x09runner run ]",
+referencedClasses: ["TestSuiteRunner", "Promise", "ResultAnnouncement", "Set"],
+//>>excludeEnd("ide");
+pragmas: [],
+messageSends: ["collect:", "selector:", "class", "on:", "timeout:", "result", "new:", "on:do:", "announcer", "ifTrue:", "and:", "==", "=", "runs", "total", "do:", "assert:equals:", "selectorSetOf:", "errors", "asSet", "failures", "new", "run"]
+}, function ($methodClass){ return function (){
+var self=this,$self=this;
+var suite,runner,result;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $1;
+suite=["fakeNonLifoReturn", "testPass"]._collect_((function(each){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+return $recv($self._class())._selector_(each);
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({each:each},$ctx1,1)});
+//>>excludeEnd("ctx");
+}));
+runner=$recv($globals.TestSuiteRunner)._on_(suite);
+$self._timeout_((200));
+result=[$recv(runner)._result()
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+,$ctx1.sendIdx["result"]=1
+//>>excludeEnd("ctx");
+][0];
+return $recv($globals.Promise)._new_((function(model){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+$recv($recv(runner)._announcer())._on_do_($globals.ResultAnnouncement,(function(ann){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx3) {
+//>>excludeEnd("ctx");
+if($core.assert($recv($recv(ann)._result()).__eq_eq(result))){
+$1=$recv($recv(result)._runs()).__eq($recv(result)._total());
+} else {
+$1=false;
+}
+if($core.assert($1)){
+return $recv(model)._do_((function(){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx4) {
+//>>excludeEnd("ctx");
+[$self._assert_equals_([$self._selectorSetOf_($recv(result)._errors())
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+,$ctx4.sendIdx["selectorSetOf:"]=1
+//>>excludeEnd("ctx");
+][0],["fakeNonLifoReturn"]._asSet())
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+,$ctx4.sendIdx["assert:equals:"]=1
+//>>excludeEnd("ctx");
+][0];
+return $self._assert_equals_($self._selectorSetOf_($recv(result)._failures()),$recv($globals.Set)._new());
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx4) {$ctx4.fillBlock({},$ctx3,6)});
+//>>excludeEnd("ctx");
+}));
+}
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx3) {$ctx3.fillBlock({ann:ann},$ctx2,3)});
+//>>excludeEnd("ctx");
+}));
+return $recv(runner)._run();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({model:model},$ctx1,2)});
+//>>excludeEnd("ctx");
+}));
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"testNonLifo",{suite:suite,runner:runner,result:result})});
+//>>excludeEnd("ctx");
+}; }),
+$globals.SUnitPromiseTest);
+
 $core.addMethod(
 $core.addMethod(
 $core.method({
 $core.method({
 selector: "testPass",
 selector: "testPass",
@@ -1363,11 +1588,11 @@ selector: "testPromiseErrorsAndFailures",
 protocol: "tests",
 protocol: "tests",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: [],
 args: [],
-source: "testPromiseErrorsAndFailures\x0a\x09| suite runner result |\x0a\x09suite := #(fakeError fakeErrorFailingInTearDown fakeFailure testPass) collect: [ :each | self class selector: each ].\x0a\x09runner := TestSuiteRunner on: suite.\x0a\x09self timeout: 200.\x0a\x09result := runner result.\x0a\x09^ Promise new: [ :model |\x0a\x09\x09runner announcer on: ResultAnnouncement do: [ :ann |\x0a\x09\x09\x09(ann result == result and: [ result runs = result total ]) ifTrue: [\x0a\x09\x09\x09\x09self assert: (self selectorSetOf: result errors) equals: #(fakeError) asSet.\x0a\x09\x09\x09\x09self assert: (self selectorSetOf: result failures) equals: #(fakeErrorFailingInTearDown fakeFailure) asSet.\x0a\x09\x09\x09\x09model value: nil ] ].\x0a\x09\x09runner run ]",
+source: "testPromiseErrorsAndFailures\x0a\x09| suite runner result |\x0a\x09suite := #(fakeError fakeErrorFailingInTearDown fakeFailure testPass) collect: [ :each | self class selector: each ].\x0a\x09runner := TestSuiteRunner on: suite.\x0a\x09self timeout: 200.\x0a\x09result := runner result.\x0a\x09^ Promise new: [ :model |\x0a\x09\x09runner announcer on: ResultAnnouncement do: [ :ann |\x0a\x09\x09\x09(ann result == result and: [ result runs = result total ]) ifTrue: [ model do: [\x0a\x09\x09\x09\x09self assert: (self selectorSetOf: result errors) equals: #(fakeError) asSet.\x0a\x09\x09\x09\x09self assert: (self selectorSetOf: result failures) equals: #(fakeErrorFailingInTearDown fakeFailure) asSet ] ] ].\x0a\x09\x09runner run ]",
 referencedClasses: ["TestSuiteRunner", "Promise", "ResultAnnouncement"],
 referencedClasses: ["TestSuiteRunner", "Promise", "ResultAnnouncement"],
 //>>excludeEnd("ide");
 //>>excludeEnd("ide");
 pragmas: [],
 pragmas: [],
-messageSends: ["collect:", "selector:", "class", "on:", "timeout:", "result", "new:", "on:do:", "announcer", "ifTrue:", "and:", "==", "=", "runs", "total", "assert:equals:", "selectorSetOf:", "errors", "asSet", "failures", "value:", "run"]
+messageSends: ["collect:", "selector:", "class", "on:", "timeout:", "result", "new:", "on:do:", "announcer", "ifTrue:", "and:", "==", "=", "runs", "total", "do:", "assert:equals:", "selectorSetOf:", "errors", "asSet", "failures", "run"]
 }, function ($methodClass){ return function (){
 }, function ($methodClass){ return function (){
 var self=this,$self=this;
 var self=this,$self=this;
 var suite,runner,result;
 var suite,runner,result;
@@ -1405,21 +1630,28 @@ $1=$recv($recv(result)._runs()).__eq($recv(result)._total());
 $1=false;
 $1=false;
 }
 }
 if($core.assert($1)){
 if($core.assert($1)){
+return $recv(model)._do_((function(){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx4) {
+//>>excludeEnd("ctx");
 [$self._assert_equals_([$self._selectorSetOf_($recv(result)._errors())
 [$self._assert_equals_([$self._selectorSetOf_($recv(result)._errors())
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-,$ctx3.sendIdx["selectorSetOf:"]=1
+,$ctx4.sendIdx["selectorSetOf:"]=1
 //>>excludeEnd("ctx");
 //>>excludeEnd("ctx");
 ][0],[["fakeError"]._asSet()
 ][0],[["fakeError"]._asSet()
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-,$ctx3.sendIdx["asSet"]=1
+,$ctx4.sendIdx["asSet"]=1
 //>>excludeEnd("ctx");
 //>>excludeEnd("ctx");
 ][0])
 ][0])
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-,$ctx3.sendIdx["assert:equals:"]=1
+,$ctx4.sendIdx["assert:equals:"]=1
 //>>excludeEnd("ctx");
 //>>excludeEnd("ctx");
 ][0];
 ][0];
-$self._assert_equals_($self._selectorSetOf_($recv(result)._failures()),["fakeErrorFailingInTearDown", "fakeFailure"]._asSet());
-return $recv(model)._value_(nil);
+return $self._assert_equals_($self._selectorSetOf_($recv(result)._failures()),["fakeErrorFailingInTearDown", "fakeFailure"]._asSet());
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx4) {$ctx4.fillBlock({},$ctx3,6)});
+//>>excludeEnd("ctx");
+}));
 }
 }
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 }, function($ctx3) {$ctx3.fillBlock({ann:ann},$ctx2,3)});
 }, function($ctx3) {$ctx3.fillBlock({ann:ann},$ctx2,3)});
@@ -1442,18 +1674,18 @@ selector: "testTimeouts",
 protocol: "tests",
 protocol: "tests",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: [],
 args: [],
-source: "testTimeouts\x0a\x09| suite runner result |\x0a\x09suite := #(fakeTimeout fakeMultipleTimeoutFailing fakeMultipleTimeoutPassing fakeTimeoutSendOnly fakePromiseWithoutTimeout testPass) collect: [ :each | self class selector: each ].\x0a\x09runner := TestSuiteRunner on: suite.\x0a\x09self timeout: 200.\x0a\x09result := runner result.\x0a\x09^ Promise new: [ :model |\x0a\x09\x09runner announcer on: ResultAnnouncement do: [ :ann |\x0a\x09\x09\x09console log: ann; log: ann result runs.\x0a\x09\x09\x09(ann result == result and: [ result runs = result total ]) ifTrue: [\x0a\x09\x09\x09\x09self assert: (self selectorSetOf: result errors) equals: #() asSet.\x0a\x09\x09\x09\x09self assert: (self selectorSetOf: result failures) equals: #(fakeMultipleTimeoutFailing fakeTimeout fakeTimeoutSendOnly fakePromiseWithoutTimeout) asSet.\x0a\x09\x09\x09\x09model value: nil ] ].\x0a\x09\x09runner run ]",
+source: "testTimeouts\x0a\x09| suite runner result |\x0a\x09suite := #(fakeTimeout fakeMultipleTimeoutFailing fakeMultipleTimeoutPassing fakeTimeoutSendOnly fakePromiseWithoutTimeout testPass) collect: [ :each | self class selector: each ].\x0a\x09runner := TestSuiteRunner on: suite.\x0a\x09self timeout: 200.\x0a\x09result := runner result.\x0a\x09^ Promise new: [ :model |\x0a\x09\x09runner announcer on: ResultAnnouncement do: [ :ann |\x0a\x09\x09\x09(ann result == result and: [ result runs = result total ]) ifTrue: [ model do: [\x0a\x09\x09\x09\x09self assert: (self selectorSetOf: result errors) equals: #() asSet.\x0a\x09\x09\x09\x09self assert: (self selectorSetOf: result failures) equals: #(fakeMultipleTimeoutFailing fakeTimeout fakeTimeoutSendOnly fakePromiseWithoutTimeout) asSet ] ] ].\x0a\x09\x09runner run ]",
 referencedClasses: ["TestSuiteRunner", "Promise", "ResultAnnouncement"],
 referencedClasses: ["TestSuiteRunner", "Promise", "ResultAnnouncement"],
 //>>excludeEnd("ide");
 //>>excludeEnd("ide");
 pragmas: [],
 pragmas: [],
-messageSends: ["collect:", "selector:", "class", "on:", "timeout:", "result", "new:", "on:do:", "announcer", "log:", "runs", "ifTrue:", "and:", "==", "=", "total", "assert:equals:", "selectorSetOf:", "errors", "asSet", "failures", "value:", "run"]
+messageSends: ["collect:", "selector:", "class", "on:", "timeout:", "result", "new:", "on:do:", "announcer", "ifTrue:", "and:", "==", "=", "runs", "total", "do:", "assert:equals:", "selectorSetOf:", "errors", "asSet", "failures", "run"]
 }, function ($methodClass){ return function (){
 }, function ($methodClass){ return function (){
 var self=this,$self=this;
 var self=this,$self=this;
 var suite,runner,result;
 var suite,runner,result;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
 //>>excludeEnd("ctx");
-var $1,$2;
+var $1;
 suite=["fakeTimeout", "fakeMultipleTimeoutFailing", "fakeMultipleTimeoutPassing", "fakeTimeoutSendOnly", "fakePromiseWithoutTimeout", "testPass"]._collect_((function(each){
 suite=["fakeTimeout", "fakeMultipleTimeoutFailing", "fakeMultipleTimeoutPassing", "fakeTimeoutSendOnly", "fakePromiseWithoutTimeout", "testPass"]._collect_((function(each){
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx2) {
 return $core.withContext(function($ctx2) {
@@ -1478,42 +1710,34 @@ $recv($recv(runner)._announcer())._on_do_($globals.ResultAnnouncement,(function(
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx3) {
 return $core.withContext(function($ctx3) {
 //>>excludeEnd("ctx");
 //>>excludeEnd("ctx");
-$1=console;
-[$recv($1)._log_(ann)
-//>>excludeStart("ctx", pragmas.excludeDebugContexts);
-,$ctx3.sendIdx["log:"]=1
-//>>excludeEnd("ctx");
-][0];
-$recv($1)._log_([$recv([$recv(ann)._result()
-//>>excludeStart("ctx", pragmas.excludeDebugContexts);
-,$ctx3.sendIdx["result"]=2
-//>>excludeEnd("ctx");
-][0])._runs()
-//>>excludeStart("ctx", pragmas.excludeDebugContexts);
-,$ctx3.sendIdx["runs"]=1
-//>>excludeEnd("ctx");
-][0]);
 if($core.assert($recv($recv(ann)._result()).__eq_eq(result))){
 if($core.assert($recv($recv(ann)._result()).__eq_eq(result))){
-$2=$recv($recv(result)._runs()).__eq($recv(result)._total());
+$1=$recv($recv(result)._runs()).__eq($recv(result)._total());
 } else {
 } else {
-$2=false;
+$1=false;
 }
 }
-if($core.assert($2)){
+if($core.assert($1)){
+return $recv(model)._do_((function(){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx4) {
+//>>excludeEnd("ctx");
 [$self._assert_equals_([$self._selectorSetOf_($recv(result)._errors())
 [$self._assert_equals_([$self._selectorSetOf_($recv(result)._errors())
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-,$ctx3.sendIdx["selectorSetOf:"]=1
+,$ctx4.sendIdx["selectorSetOf:"]=1
 //>>excludeEnd("ctx");
 //>>excludeEnd("ctx");
 ][0],[[]._asSet()
 ][0],[[]._asSet()
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-,$ctx3.sendIdx["asSet"]=1
+,$ctx4.sendIdx["asSet"]=1
 //>>excludeEnd("ctx");
 //>>excludeEnd("ctx");
 ][0])
 ][0])
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-,$ctx3.sendIdx["assert:equals:"]=1
+,$ctx4.sendIdx["assert:equals:"]=1
 //>>excludeEnd("ctx");
 //>>excludeEnd("ctx");
 ][0];
 ][0];
-$self._assert_equals_($self._selectorSetOf_($recv(result)._failures()),["fakeMultipleTimeoutFailing", "fakeTimeout", "fakeTimeoutSendOnly", "fakePromiseWithoutTimeout"]._asSet());
-return $recv(model)._value_(nil);
+return $self._assert_equals_($self._selectorSetOf_($recv(result)._failures()),["fakeMultipleTimeoutFailing", "fakeTimeout", "fakeTimeoutSendOnly", "fakePromiseWithoutTimeout"]._asSet());
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx4) {$ctx4.fillBlock({},$ctx3,6)});
+//>>excludeEnd("ctx");
+}));
 }
 }
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 }, function($ctx3) {$ctx3.fillBlock({ann:ann},$ctx2,3)});
 }, function($ctx3) {$ctx3.fillBlock({ann:ann},$ctx2,3)});

+ 50 - 7
lang/src/SUnit-Tests.st

@@ -93,6 +93,12 @@ fakeMultipleTimeoutPassing
 	]) valueWithTimeout: 10
 	]) valueWithTimeout: 10
 !
 !
 
 
+fakeNonLifoReturn
+	flag := 'bad'.
+	self timeout: 30.
+	flag := (self async: [ flag := 'ok'. ^ 'non-lifo' ]) valueWithTimeout: 20
+!
+
 fakeTimeout
 fakeTimeout
 	self timeout: 10.
 	self timeout: 10.
 	(self async: [ self finished ]) valueWithTimeout: 20
 	(self async: [ self finished ]) valueWithTimeout: 20
@@ -153,6 +159,23 @@ testIsAsyncReturnsCorrectValues
 	self deny: self isAsync
 	self deny: self isAsync
 !
 !
 
 
+testNonLifo
+	| suite runner result assertBlock |
+	suite := #(fakeNonLifoReturn testPass) collect: [ :each | self class selector: each ].
+	runner := TestSuiteRunner on: suite.
+	self timeout: 200.
+	result := runner result.
+	assertBlock := self async: [
+		self assert: (self selectorSetOf: result errors) equals: #(fakeNonLifoReturn) asSet.
+		self assert: (self selectorSetOf: result failures) equals: Set new.
+		"TODO check that error is indeed a correct NonLifoReturn"
+		self finished
+	].
+	runner announcer on: ResultAnnouncement do: [ :ann |
+		(ann result == result and: [ result runs = result total ]) ifTrue: assertBlock ].
+	runner run
+!
+
 testPass
 testPass
 	flag := 'bad'.
 	flag := 'bad'.
 	self timeout: 10.
 	self timeout: 10.
@@ -221,6 +244,13 @@ fakeMultipleTimeoutPassing
 	^ (Promise delayMilliseconds: 10) then: [ self timeout: 40. Promise delayMilliseconds: 20 ]
 	^ (Promise delayMilliseconds: 10) then: [ self timeout: 40. Promise delayMilliseconds: 20 ]
 !
 !
 
 
+fakeNonLifoReturn
+	flag := 'bad'.
+	self timeout: 30.
+	flag := Promise delayMilliseconds: 20.
+	^ flag then: [ flag := 'ok'. ^ 'non-lifo' ]
+!
+
 fakePromiseWithoutTimeout
 fakePromiseWithoutTimeout
 	^ Promise delayMilliseconds: 10
 	^ Promise delayMilliseconds: 10
 !
 !
@@ -261,6 +291,22 @@ testIsAsyncReturnsCorrectValues
 	^ Promise new
 	^ Promise new
 !
 !
 
 
+testNonLifo
+	| suite runner result |
+	suite := #(fakeNonLifoReturn testPass) collect: [ :each | self class selector: each ].
+	runner := TestSuiteRunner on: suite.
+	self timeout: 200.
+	result := runner result.
+	^ Promise new: [ :model |
+		runner announcer on: ResultAnnouncement do: [ :ann |
+			(ann result == result and: [ result runs = result total ]) ifTrue: [ model do: [
+				self assert: (self selectorSetOf: result errors) equals: #(fakeNonLifoReturn) asSet.
+				self assert: (self selectorSetOf: result failures) equals: Set new.
+				"TODO check that error is indeed a correct NonLifoReturn"
+		] ] ].
+		runner run ]
+!
+
 testPass
 testPass
 	flag := 'bad'.
 	flag := 'bad'.
 	self timeout: 10.
 	self timeout: 10.
@@ -276,10 +322,9 @@ testPromiseErrorsAndFailures
 	result := runner result.
 	result := runner result.
 	^ Promise new: [ :model |
 	^ Promise new: [ :model |
 		runner announcer on: ResultAnnouncement do: [ :ann |
 		runner announcer on: ResultAnnouncement do: [ :ann |
-			(ann result == result and: [ result runs = result total ]) ifTrue: [
+			(ann result == result and: [ result runs = result total ]) ifTrue: [ model do: [
 				self assert: (self selectorSetOf: result errors) equals: #(fakeError) asSet.
 				self assert: (self selectorSetOf: result errors) equals: #(fakeError) asSet.
-				self assert: (self selectorSetOf: result failures) equals: #(fakeErrorFailingInTearDown fakeFailure) asSet.
-				model value: nil ] ].
+				self assert: (self selectorSetOf: result failures) equals: #(fakeErrorFailingInTearDown fakeFailure) asSet ] ] ].
 		runner run ]
 		runner run ]
 !
 !
 
 
@@ -291,11 +336,9 @@ testTimeouts
 	result := runner result.
 	result := runner result.
 	^ Promise new: [ :model |
 	^ Promise new: [ :model |
 		runner announcer on: ResultAnnouncement do: [ :ann |
 		runner announcer on: ResultAnnouncement do: [ :ann |
-			console log: ann; log: ann result runs.
-			(ann result == result and: [ result runs = result total ]) ifTrue: [
+			(ann result == result and: [ result runs = result total ]) ifTrue: [ model do: [
 				self assert: (self selectorSetOf: result errors) equals: #() asSet.
 				self assert: (self selectorSetOf: result errors) equals: #() asSet.
-				self assert: (self selectorSetOf: result failures) equals: #(fakeMultipleTimeoutFailing fakeTimeout fakeTimeoutSendOnly fakePromiseWithoutTimeout) asSet.
-				model value: nil ] ].
+				self assert: (self selectorSetOf: result failures) equals: #(fakeMultipleTimeoutFailing fakeTimeout fakeTimeoutSendOnly fakePromiseWithoutTimeout) asSet ] ] ].
 		runner run ]
 		runner run ]
 ! !
 ! !
 
 

+ 31 - 2
lang/src/SUnit.js

@@ -1355,11 +1355,11 @@ $globals.ReportingTestContext);
 
 
 $core.addMethod(
 $core.addMethod(
 $core.method({
 $core.method({
-selector: "withErrorReporting:",
+selector: "withErrorReporting2:",
 protocol: "private",
 protocol: "private",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["aBlock"],
 args: ["aBlock"],
-source: "withErrorReporting: aBlock\x0a\x09[ aBlock\x0a\x09\x09on: TestFailure\x0a\x09\x09do: [ :ex | result addFailure: testCase ]\x0a\x09]\x0a\x09\x09on: Error\x0a\x09\x09do: [ :ex | result addError: testCase ]",
+source: "withErrorReporting2: aBlock\x0a\x09[ aBlock\x0a\x09\x09on: TestFailure\x0a\x09\x09do: [ :ex | result addFailure: testCase ]\x0a\x09]\x0a\x09\x09on: Error\x0a\x09\x09do: [ :ex | result addError: testCase ]",
 referencedClasses: ["TestFailure", "Error"],
 referencedClasses: ["TestFailure", "Error"],
 //>>excludeEnd("ide");
 //>>excludeEnd("ide");
 pragmas: [],
 pragmas: [],
@@ -1400,6 +1400,35 @@ return $recv($self.result)._addError_($self.testCase);
 ][0];
 ][0];
 return self;
 return self;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"withErrorReporting2:",{aBlock:aBlock})});
+//>>excludeEnd("ctx");
+}; }),
+$globals.ReportingTestContext);
+
+$core.addMethod(
+$core.method({
+selector: "withErrorReporting:",
+protocol: "private",
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aBlock"],
+source: "withErrorReporting: aBlock\x0a\x09<inlineJS: '\x0a\x09try { aBlock._value(); }\x0a\x09catch (ex) {\x0a\x09\x09var smalltalkError = $globals.Smalltalk._asSmalltalkException_(ex);\x0a\x09\x09self._withErrorReporting2_(function () { smalltalkError._pass(); });\x0a\x09}'>",
+referencedClasses: [],
+//>>excludeEnd("ide");
+pragmas: [["inlineJS:", ["\x0a\x09try { aBlock._value(); }\x0a\x09catch (ex) {\x0a\x09\x09var smalltalkError = $globals.Smalltalk._asSmalltalkException_(ex);\x0a\x09\x09self._withErrorReporting2_(function () { smalltalkError._pass(); });\x0a\x09}"]]],
+messageSends: []
+}, function ($methodClass){ return function (aBlock){
+var self=this,$self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+
+	try { aBlock._value(); }
+	catch (ex) {
+		var smalltalkError = $globals.Smalltalk._asSmalltalkException_(ex);
+		self._withErrorReporting2_(function () { smalltalkError._pass(); });
+	};
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
 }, function($ctx1) {$ctx1.fill(self,"withErrorReporting:",{aBlock:aBlock})});
 }, function($ctx1) {$ctx1.fill(self,"withErrorReporting:",{aBlock:aBlock})});
 //>>excludeEnd("ctx");
 //>>excludeEnd("ctx");
 }; }),
 }; }),

+ 10 - 1
lang/src/SUnit.st

@@ -335,13 +335,22 @@ result: aTestResult
 
 
 !ReportingTestContext methodsFor: 'private'!
 !ReportingTestContext methodsFor: 'private'!
 
 
-withErrorReporting: aBlock
+withErrorReporting2: aBlock
 	[ aBlock
 	[ aBlock
 		on: TestFailure
 		on: TestFailure
 		do: [ :ex | result addFailure: testCase ]
 		do: [ :ex | result addFailure: testCase ]
 	]
 	]
 		on: Error
 		on: Error
 		do: [ :ex | result addError: testCase ]
 		do: [ :ex | result addError: testCase ]
+!
+
+withErrorReporting: aBlock
+	<inlineJS: '
+	try { aBlock._value(); }
+	catch (ex) {
+		var smalltalkError = $globals.Smalltalk._asSmalltalkException_(ex);
+		self._withErrorReporting2_(function () { smalltalkError._pass(); });
+	}'>
 ! !
 ! !
 
 
 !ReportingTestContext methodsFor: 'running'!
 !ReportingTestContext methodsFor: 'running'!