Browse Source

Async SUnit tests, sans timeout.

Herby Vojčík 11 years ago
parent
commit
96416f75d9
8 changed files with 733 additions and 16 deletions
  1. 166 0
      js/SUnit-Tests.deploy.js
  2. 211 0
      js/SUnit-Tests.js
  3. 103 3
      js/SUnit.deploy.js
  4. 136 6
      js/SUnit.js
  5. 2 1
      js/amber.js
  6. 74 0
      st/SUnit-Tests.st
  7. 40 5
      st/SUnit.st
  8. 1 1
      test/run_build.sh

+ 166 - 0
js/SUnit-Tests.deploy.js

@@ -0,0 +1,166 @@
+smalltalk.addPackage('SUnit-Tests', {});
+smalltalk.addClass('SUnitAsyncTest', smalltalk.TestCase, ['flag'], 'SUnit-Tests');
+smalltalk.addMethod(
+"_fakeError",
+smalltalk.method({
+selector: "fakeError",
+fn: function (){
+var self=this;
+self["@flag"]="bad";
+smalltalk.send(self,"_graceTime_",[(10)]);
+self["@flag"]=smalltalk.send(smalltalk.send(self,"_async_",[(function(){
+smalltalk.send(self,"_error_",["Intentional"]);
+smalltalk.send(self,"_finished",[]);
+self["@flag"]="ok";
+return self["@flag"];
+})]),"_valueWithTimeout_",[(5)]);
+return self}
+}),
+smalltalk.SUnitAsyncTest);
+
+smalltalk.addMethod(
+"_fakeFailure",
+smalltalk.method({
+selector: "fakeFailure",
+fn: function (){
+var self=this;
+self["@flag"]="bad";
+smalltalk.send(self,"_graceTime_",[(10)]);
+self["@flag"]=smalltalk.send(smalltalk.send(self,"_async_",[(function(){
+smalltalk.send(self,"_assert_",[false]);
+smalltalk.send(self,"_finished",[]);
+self["@flag"]="ok";
+return self["@flag"];
+})]),"_valueWithTimeout_",[(5)]);
+return self}
+}),
+smalltalk.SUnitAsyncTest);
+
+smalltalk.addMethod(
+"_setUp",
+smalltalk.method({
+selector: "setUp",
+fn: function (){
+var self=this;
+self["@flag"]="ok";
+return self}
+}),
+smalltalk.SUnitAsyncTest);
+
+smalltalk.addMethod(
+"_tearDown",
+smalltalk.method({
+selector: "tearDown",
+fn: function (){
+var self=this;
+smalltalk.send(self,"_assert_equals_",["ok",self["@flag"]]);
+return self}
+}),
+smalltalk.SUnitAsyncTest);
+
+smalltalk.addMethod(
+"_testAsyncErrorsAndFailuresWork",
+smalltalk.method({
+selector: "testAsyncErrorsAndFailuresWork",
+fn: function (){
+var self=this;
+var $1,$2;
+var suite;
+var runner;
+var result;
+var assertBlock;
+suite=[smalltalk.send(smalltalk.send(self,"_class",[]),"_selector_",["fakeError"]),smalltalk.send(smalltalk.send(self,"_class",[]),"_selector_",["fakeFailure"]),smalltalk.send(smalltalk.send(self,"_class",[]),"_selector_",["testPass"])];
+runner=smalltalk.send((smalltalk.TestSuiteRunner || TestSuiteRunner),"_on_",[suite]);
+smalltalk.send(self,"_graceTime_",[(200)]);
+result=smalltalk.send(runner,"_result",[]);
+assertBlock=smalltalk.send(self,"_async_",[(function(){
+smalltalk.send(self,"_assert_equals_",[(1),smalltalk.send(smalltalk.send(result,"_errors",[]),"_size",[])]);
+smalltalk.send(self,"_assert_equals_",["fakeError",smalltalk.send(smalltalk.send(smalltalk.send(result,"_errors",[]),"_first",[]),"_selector",[])]);
+smalltalk.send(self,"_assert_equals_",[(1),smalltalk.send(smalltalk.send(result,"_failures",[]),"_size",[])]);
+smalltalk.send(self,"_assert_equals_",["fakeFailure",smalltalk.send(smalltalk.send(smalltalk.send(result,"_failures",[]),"_first",[]),"_selector",[])]);
+return smalltalk.send(self,"_finished",[]);
+})]);
+smalltalk.send(smalltalk.send(runner,"_announcer",[]),"_on_do_",[(smalltalk.ResultAnnouncement || ResultAnnouncement),(function(ann){
+$1=smalltalk.send(smalltalk.send(ann,"_result",[]),"__eq_eq",[result]);
+if(smalltalk.assert($1)){
+$2=smalltalk.send(smalltalk.send(result,"_runs",[]),"__eq",[smalltalk.send(result,"_total",[])]);
+return smalltalk.send($2,"_ifTrue_",[assertBlock]);
+};
+})]);
+smalltalk.send(runner,"_run",[]);
+return self}
+}),
+smalltalk.SUnitAsyncTest);
+
+smalltalk.addMethod(
+"_testAsyncNeedsGraceTime",
+smalltalk.method({
+selector: "testAsyncNeedsGraceTime",
+fn: function (){
+var self=this;
+smalltalk.send(self,"_should_raise_",[(function(){
+return smalltalk.send(self,"_async_",[(function(){
+})]);
+}),(smalltalk.Error || Error)]);
+smalltalk.send(self,"_graceTime_",[(0)]);
+smalltalk.send(self,"_shouldnt_raise_",[(function(){
+return smalltalk.send(self,"_async_",[(function(){
+})]);
+}),(smalltalk.Error || Error)]);
+smalltalk.send(self,"_finished",[]);
+return self}
+}),
+smalltalk.SUnitAsyncTest);
+
+smalltalk.addMethod(
+"_testFinishedNeedsGraceTime",
+smalltalk.method({
+selector: "testFinishedNeedsGraceTime",
+fn: function (){
+var self=this;
+smalltalk.send(self,"_should_raise_",[(function(){
+return smalltalk.send(self,"_finished",[]);
+}),(smalltalk.Error || Error)]);
+smalltalk.send(self,"_graceTime_",[(0)]);
+smalltalk.send(self,"_shouldnt_raise_",[(function(){
+return smalltalk.send(self,"_finished",[]);
+}),(smalltalk.Error || Error)]);
+return self}
+}),
+smalltalk.SUnitAsyncTest);
+
+smalltalk.addMethod(
+"_testIsAsyncReturnsCorrectValues",
+smalltalk.method({
+selector: "testIsAsyncReturnsCorrectValues",
+fn: function (){
+var self=this;
+smalltalk.send(self,"_deny_",[smalltalk.send(self,"_isAsync",[])]);
+smalltalk.send(self,"_graceTime_",[(0)]);
+smalltalk.send(self,"_assert_",[smalltalk.send(self,"_isAsync",[])]);
+smalltalk.send(self,"_finished",[]);
+smalltalk.send(self,"_deny_",[smalltalk.send(self,"_isAsync",[])]);
+return self}
+}),
+smalltalk.SUnitAsyncTest);
+
+smalltalk.addMethod(
+"_testPass",
+smalltalk.method({
+selector: "testPass",
+fn: function (){
+var self=this;
+self["@flag"]="bad";
+smalltalk.send(self,"_graceTime_",[(10)]);
+self["@flag"]=smalltalk.send(smalltalk.send(self,"_async_",[(function(){
+smalltalk.send(self,"_assert_",[true]);
+smalltalk.send(self,"_finished",[]);
+self["@flag"]="ok";
+return self["@flag"];
+})]),"_valueWithTimeout_",[(5)]);
+return self}
+}),
+smalltalk.SUnitAsyncTest);
+
+
+

+ 211 - 0
js/SUnit-Tests.js

@@ -0,0 +1,211 @@
+smalltalk.addPackage('SUnit-Tests', {});
+smalltalk.addClass('SUnitAsyncTest', smalltalk.TestCase, ['flag'], 'SUnit-Tests');
+smalltalk.addMethod(
+"_fakeError",
+smalltalk.method({
+selector: "fakeError",
+category: 'tests',
+fn: function (){
+var self=this;
+self["@flag"]="bad";
+smalltalk.send(self,"_graceTime_",[(10)]);
+self["@flag"]=smalltalk.send(smalltalk.send(self,"_async_",[(function(){
+smalltalk.send(self,"_error_",["Intentional"]);
+smalltalk.send(self,"_finished",[]);
+self["@flag"]="ok";
+return self["@flag"];
+})]),"_valueWithTimeout_",[(5)]);
+return self},
+args: [],
+source: "fakeError\x0a\x09flag := 'bad'.\x0a\x09self graceTime: 10.\x0a    flag := (self async: [ self error: 'Intentional'. self finished. flag := 'ok' ]) valueWithTimeout: 5\x0a",
+messageSends: ["graceTime:", "valueWithTimeout:", "async:", "error:", "finished"],
+referencedClasses: []
+}),
+smalltalk.SUnitAsyncTest);
+
+smalltalk.addMethod(
+"_fakeFailure",
+smalltalk.method({
+selector: "fakeFailure",
+category: 'tests',
+fn: function (){
+var self=this;
+self["@flag"]="bad";
+smalltalk.send(self,"_graceTime_",[(10)]);
+self["@flag"]=smalltalk.send(smalltalk.send(self,"_async_",[(function(){
+smalltalk.send(self,"_assert_",[false]);
+smalltalk.send(self,"_finished",[]);
+self["@flag"]="ok";
+return self["@flag"];
+})]),"_valueWithTimeout_",[(5)]);
+return self},
+args: [],
+source: "fakeFailure\x0a\x09flag := 'bad'.\x0a\x09self graceTime: 10.\x0a    flag := (self async: [ self assert: false. self finished. flag := 'ok' ]) valueWithTimeout: 5\x0a",
+messageSends: ["graceTime:", "valueWithTimeout:", "async:", "assert:", "finished"],
+referencedClasses: []
+}),
+smalltalk.SUnitAsyncTest);
+
+smalltalk.addMethod(
+"_setUp",
+smalltalk.method({
+selector: "setUp",
+category: 'running',
+fn: function (){
+var self=this;
+self["@flag"]="ok";
+return self},
+args: [],
+source: "setUp\x0a\x09flag := 'ok'\x0a",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.SUnitAsyncTest);
+
+smalltalk.addMethod(
+"_tearDown",
+smalltalk.method({
+selector: "tearDown",
+category: 'running',
+fn: function (){
+var self=this;
+smalltalk.send(self,"_assert_equals_",["ok",self["@flag"]]);
+return self},
+args: [],
+source: "tearDown\x0a\x09self assert: 'ok' equals: flag\x0a",
+messageSends: ["assert:equals:"],
+referencedClasses: []
+}),
+smalltalk.SUnitAsyncTest);
+
+smalltalk.addMethod(
+"_testAsyncErrorsAndFailuresWork",
+smalltalk.method({
+selector: "testAsyncErrorsAndFailuresWork",
+category: 'tests',
+fn: function (){
+var self=this;
+var $1,$2;
+var suite;
+var runner;
+var result;
+var assertBlock;
+suite=[smalltalk.send(smalltalk.send(self,"_class",[]),"_selector_",["fakeError"]),smalltalk.send(smalltalk.send(self,"_class",[]),"_selector_",["fakeFailure"]),smalltalk.send(smalltalk.send(self,"_class",[]),"_selector_",["testPass"])];
+runner=smalltalk.send((smalltalk.TestSuiteRunner || TestSuiteRunner),"_on_",[suite]);
+smalltalk.send(self,"_graceTime_",[(200)]);
+result=smalltalk.send(runner,"_result",[]);
+assertBlock=smalltalk.send(self,"_async_",[(function(){
+smalltalk.send(self,"_assert_equals_",[(1),smalltalk.send(smalltalk.send(result,"_errors",[]),"_size",[])]);
+smalltalk.send(self,"_assert_equals_",["fakeError",smalltalk.send(smalltalk.send(smalltalk.send(result,"_errors",[]),"_first",[]),"_selector",[])]);
+smalltalk.send(self,"_assert_equals_",[(1),smalltalk.send(smalltalk.send(result,"_failures",[]),"_size",[])]);
+smalltalk.send(self,"_assert_equals_",["fakeFailure",smalltalk.send(smalltalk.send(smalltalk.send(result,"_failures",[]),"_first",[]),"_selector",[])]);
+return smalltalk.send(self,"_finished",[]);
+})]);
+smalltalk.send(smalltalk.send(runner,"_announcer",[]),"_on_do_",[(smalltalk.ResultAnnouncement || ResultAnnouncement),(function(ann){
+$1=smalltalk.send(smalltalk.send(ann,"_result",[]),"__eq_eq",[result]);
+if(smalltalk.assert($1)){
+$2=smalltalk.send(smalltalk.send(result,"_runs",[]),"__eq",[smalltalk.send(result,"_total",[])]);
+return smalltalk.send($2,"_ifTrue_",[assertBlock]);
+};
+})]);
+smalltalk.send(runner,"_run",[]);
+return self},
+args: [],
+source: "testAsyncErrorsAndFailuresWork\x0a\x09| suite runner result assertBlock |\x0a\x09suite := { self class selector: 'fakeError'. self class selector: 'fakeFailure'. self class selector: 'testPass' }.\x0a    runner := TestSuiteRunner on: suite.\x0a    self graceTime: 200.\x0a\x09result := runner result.\x0a    assertBlock := self async: [\x0a\x09\x09self assert: 1 equals: result errors size.\x0a\x09\x09self assert: 'fakeError' equals: result errors first selector.\x0a\x09\x09self assert: 1 equals: result failures size.\x0a\x09\x09self assert: 'fakeFailure' equals: result failures first selector.\x0a\x09\x09self finished\x0a  \x09].\x0a    runner announcer on: ResultAnnouncement do: [:ann |\x0a    \x09ann result == result  ifTrue: [ result runs = result total ifTrue: assertBlock ]].\x0a\x09runner run",
+messageSends: ["selector:", "class", "on:", "graceTime:", "result", "async:", "assert:equals:", "size", "errors", "selector", "first", "failures", "finished", "on:do:", "ifTrue:", "=", "total", "runs", "==", "announcer", "run"],
+referencedClasses: ["TestSuiteRunner", "ResultAnnouncement"]
+}),
+smalltalk.SUnitAsyncTest);
+
+smalltalk.addMethod(
+"_testAsyncNeedsGraceTime",
+smalltalk.method({
+selector: "testAsyncNeedsGraceTime",
+category: 'tests',
+fn: function (){
+var self=this;
+smalltalk.send(self,"_should_raise_",[(function(){
+return smalltalk.send(self,"_async_",[(function(){
+})]);
+}),(smalltalk.Error || Error)]);
+smalltalk.send(self,"_graceTime_",[(0)]);
+smalltalk.send(self,"_shouldnt_raise_",[(function(){
+return smalltalk.send(self,"_async_",[(function(){
+})]);
+}),(smalltalk.Error || Error)]);
+smalltalk.send(self,"_finished",[]);
+return self},
+args: [],
+source: "testAsyncNeedsGraceTime\x0a    self should: [ self async: [ ] ] raise: Error.\x0a    self graceTime: 0.\x0a    self shouldnt: [ self async: [ ] ] raise: Error.\x0a    self finished\x0a",
+messageSends: ["should:raise:", "async:", "graceTime:", "shouldnt:raise:", "finished"],
+referencedClasses: ["Error"]
+}),
+smalltalk.SUnitAsyncTest);
+
+smalltalk.addMethod(
+"_testFinishedNeedsGraceTime",
+smalltalk.method({
+selector: "testFinishedNeedsGraceTime",
+category: 'tests',
+fn: function (){
+var self=this;
+smalltalk.send(self,"_should_raise_",[(function(){
+return smalltalk.send(self,"_finished",[]);
+}),(smalltalk.Error || Error)]);
+smalltalk.send(self,"_graceTime_",[(0)]);
+smalltalk.send(self,"_shouldnt_raise_",[(function(){
+return smalltalk.send(self,"_finished",[]);
+}),(smalltalk.Error || Error)]);
+return self},
+args: [],
+source: "testFinishedNeedsGraceTime\x0a    self should: [ self finished ] raise: Error.\x0a    self graceTime: 0.\x0a    self shouldnt: [ self finished ] raise: Error.\x0a",
+messageSends: ["should:raise:", "finished", "graceTime:", "shouldnt:raise:"],
+referencedClasses: ["Error"]
+}),
+smalltalk.SUnitAsyncTest);
+
+smalltalk.addMethod(
+"_testIsAsyncReturnsCorrectValues",
+smalltalk.method({
+selector: "testIsAsyncReturnsCorrectValues",
+category: 'tests',
+fn: function (){
+var self=this;
+smalltalk.send(self,"_deny_",[smalltalk.send(self,"_isAsync",[])]);
+smalltalk.send(self,"_graceTime_",[(0)]);
+smalltalk.send(self,"_assert_",[smalltalk.send(self,"_isAsync",[])]);
+smalltalk.send(self,"_finished",[]);
+smalltalk.send(self,"_deny_",[smalltalk.send(self,"_isAsync",[])]);
+return self},
+args: [],
+source: "testIsAsyncReturnsCorrectValues\x0a    self deny: self isAsync.\x0a    self graceTime: 0.\x0a    self assert: self isAsync.\x0a    self finished.\x0a    self deny: self isAsync\x0a",
+messageSends: ["deny:", "isAsync", "graceTime:", "assert:", "finished"],
+referencedClasses: []
+}),
+smalltalk.SUnitAsyncTest);
+
+smalltalk.addMethod(
+"_testPass",
+smalltalk.method({
+selector: "testPass",
+category: 'tests',
+fn: function (){
+var self=this;
+self["@flag"]="bad";
+smalltalk.send(self,"_graceTime_",[(10)]);
+self["@flag"]=smalltalk.send(smalltalk.send(self,"_async_",[(function(){
+smalltalk.send(self,"_assert_",[true]);
+smalltalk.send(self,"_finished",[]);
+self["@flag"]="ok";
+return self["@flag"];
+})]),"_valueWithTimeout_",[(5)]);
+return self},
+args: [],
+source: "testPass\x0a\x09flag := 'bad'.\x0a\x09self graceTime: 10.\x0a    flag := (self async: [ self assert: true. self finished. flag := 'ok' ]) valueWithTimeout: 5\x0a",
+messageSends: ["graceTime:", "valueWithTimeout:", "async:", "assert:", "finished"],
+referencedClasses: []
+}),
+smalltalk.SUnitAsyncTest);
+
+
+

+ 103 - 3
js/SUnit.deploy.js

@@ -31,20 +31,39 @@ smalltalk.method({
 selector: "execute:",
 fn: function (aBlock){
 var self=this;
+var $1,$2,$3,$4;
+smalltalk.send(self["@testCase"],"_context_",[self]);
 smalltalk.send((function(){
 return smalltalk.send((function(){
 return smalltalk.send((function(){
 return smalltalk.send(aBlock,"_ensure_",[(function(){
-smalltalk.send(self["@result"],"_increaseRuns",[]);
+smalltalk.send(self["@testCase"],"_context_",[nil]);
+$1=smalltalk.send(self["@testCase"],"_isAsync",[]);
+if(! smalltalk.assert($1)){
 return smalltalk.send(self["@testCase"],"_tearDown",[]);
+};
 })]);
 }),"_on_do_",[(smalltalk.TestFailure || TestFailure),(function(ex){
+$2=smalltalk.send(self["@testCase"],"_isAsync",[]);
+if(smalltalk.assert($2)){
+smalltalk.send(self["@testCase"],"_finished",[]);
+};
 return smalltalk.send(self["@result"],"_addFailure_",[self["@testCase"]]);
 })]);
 }),"_on_do_",[(smalltalk.Error || Error),(function(ex){
+$3=smalltalk.send(self["@testCase"],"_isAsync",[]);
+if(smalltalk.assert($3)){
+smalltalk.send(self["@testCase"],"_finished",[]);
+};
 return smalltalk.send(self["@result"],"_addError_",[self["@testCase"]]);
 })]);
-}),"_ensure_",[self["@finished"]]);
+}),"_ensure_",[(function(){
+$4=smalltalk.send(self["@testCase"],"_isAsync",[]);
+if(! smalltalk.assert($4)){
+smalltalk.send(self["@result"],"_increaseRuns",[]);
+return smalltalk.send(self["@finished"],"_value",[]);
+};
+})]);
 return self}
 }),
 smalltalk.RunningTestContext);
@@ -116,7 +135,7 @@ return $1;
 smalltalk.RunningTestContext.klass);
 
 
-smalltalk.addClass('TestCase', smalltalk.Object, ['testSelector'], 'SUnit');
+smalltalk.addClass('TestCase', smalltalk.Object, ['testSelector', 'asyncTimeout', 'context'], 'SUnit');
 smalltalk.addMethod(
 "_assert_",
 smalltalk.method({
@@ -156,6 +175,35 @@ fn: function (expected, actual) {
 }),
 smalltalk.TestCase);
 
+smalltalk.addMethod(
+"_async_",
+smalltalk.method({
+selector: "async:",
+fn: function (aBlock){
+var self=this;
+var $1;
+var c;
+smalltalk.send(self,"_mustBeAsync_",["#async"]);
+c=self["@context"];
+$1=(function(){
+return smalltalk.send(c,"_execute_",[aBlock]);
+});
+return $1;
+}
+}),
+smalltalk.TestCase);
+
+smalltalk.addMethod(
+"_context_",
+smalltalk.method({
+selector: "context:",
+fn: function (aRunningTestContext){
+var self=this;
+self["@context"]=aRunningTestContext;
+return self}
+}),
+smalltalk.TestCase);
+
 smalltalk.addMethod(
 "_deny_",
 smalltalk.method({
@@ -168,12 +216,64 @@ fn: function (aBoolean) {
 }),
 smalltalk.TestCase);
 
+smalltalk.addMethod(
+"_finished",
+smalltalk.method({
+selector: "finished",
+fn: function (){
+var self=this;
+smalltalk.send(self,"_mustBeAsync_",["#finished"]);
+self["@asyncTimeout"]=nil;
+return self}
+}),
+smalltalk.TestCase);
+
+smalltalk.addMethod(
+"_graceTime_",
+smalltalk.method({
+selector: "graceTime:",
+fn: function (millis){
+var self=this;
+self["@asyncTimeout"]=true;
+return self}
+}),
+smalltalk.TestCase);
+
+smalltalk.addMethod(
+"_isAsync",
+smalltalk.method({
+selector: "isAsync",
+fn: function (){
+var self=this;
+var $1;
+$1=smalltalk.send(self["@asyncTimeout"],"_notNil",[]);
+return $1;
+}
+}),
+smalltalk.TestCase);
+
+smalltalk.addMethod(
+"_mustBeAsync_",
+smalltalk.method({
+selector: "mustBeAsync:",
+fn: function (aString){
+var self=this;
+var $1;
+$1=smalltalk.send(self,"_isAsync",[]);
+if(! smalltalk.assert($1)){
+smalltalk.send(self,"_error_",[smalltalk.send(aString,"__comma",[" used without prior #graceTime:"])]);
+};
+return self}
+}),
+smalltalk.TestCase);
+
 smalltalk.addMethod(
 "_performTest",
 smalltalk.method({
 selector: "performTest",
 fn: function (){
 var self=this;
+self["@asyncTimeout"]=nil;
 smalltalk.send(self,"_perform_",[smalltalk.send(self,"_selector",[])]);
 return self}
 }),

+ 136 - 6
js/SUnit.js

@@ -42,24 +42,43 @@ selector: "execute:",
 category: 'running',
 fn: function (aBlock){
 var self=this;
+var $1,$2,$3,$4;
+smalltalk.send(self["@testCase"],"_context_",[self]);
 smalltalk.send((function(){
 return smalltalk.send((function(){
 return smalltalk.send((function(){
 return smalltalk.send(aBlock,"_ensure_",[(function(){
-smalltalk.send(self["@result"],"_increaseRuns",[]);
+smalltalk.send(self["@testCase"],"_context_",[nil]);
+$1=smalltalk.send(self["@testCase"],"_isAsync",[]);
+if(! smalltalk.assert($1)){
 return smalltalk.send(self["@testCase"],"_tearDown",[]);
+};
 })]);
 }),"_on_do_",[(smalltalk.TestFailure || TestFailure),(function(ex){
+$2=smalltalk.send(self["@testCase"],"_isAsync",[]);
+if(smalltalk.assert($2)){
+smalltalk.send(self["@testCase"],"_finished",[]);
+};
 return smalltalk.send(self["@result"],"_addFailure_",[self["@testCase"]]);
 })]);
 }),"_on_do_",[(smalltalk.Error || Error),(function(ex){
+$3=smalltalk.send(self["@testCase"],"_isAsync",[]);
+if(smalltalk.assert($3)){
+smalltalk.send(self["@testCase"],"_finished",[]);
+};
 return smalltalk.send(self["@result"],"_addError_",[self["@testCase"]]);
 })]);
-}),"_ensure_",[self["@finished"]]);
+}),"_ensure_",[(function(){
+$4=smalltalk.send(self["@testCase"],"_isAsync",[]);
+if(! smalltalk.assert($4)){
+smalltalk.send(self["@result"],"_increaseRuns",[]);
+return smalltalk.send(self["@finished"],"_value",[]);
+};
+})]);
 return self},
 args: ["aBlock"],
-source: "execute: aBlock\x0a\x09[[[\x09aBlock\x0a        \x09ensure: [ result increaseRuns. testCase tearDown ]]\x0a\x09\x09\x09on: TestFailure do: [:ex | result addFailure: testCase]]\x0a\x09\x09\x09on: Error do: [:ex | result addError: testCase]]\x0a\x09\x09\x09ensure: finished",
-messageSends: ["ensure:", "on:do:", "addError:", "addFailure:", "increaseRuns", "tearDown"],
+source: "execute: aBlock\x0a    testCase context: self.\x0a\x09[[[\x09aBlock\x0a        \x09ensure: [ testCase context: nil.\x0a\x09\x09\x09\x09testCase isAsync ifFalse: [ testCase tearDown ]]]\x0a\x09\x09\x09on: TestFailure do: [:ex | testCase isAsync ifTrue: [ testCase finished ]. result addFailure: testCase]]\x0a\x09\x09\x09on: Error do: [:ex | testCase isAsync ifTrue: [ testCase finished ]. result addError: testCase]]\x0a\x09\x09\x09ensure: [ testCase isAsync ifFalse: [\x0a                result increaseRuns.\x0a                finished value ]]",
+messageSends: ["context:", "ensure:", "ifFalse:", "increaseRuns", "value", "isAsync", "on:do:", "ifTrue:", "finished", "addError:", "addFailure:", "tearDown"],
 referencedClasses: ["Error", "TestFailure"]
 }),
 smalltalk.RunningTestContext);
@@ -156,7 +175,7 @@ referencedClasses: []
 smalltalk.RunningTestContext.klass);
 
 
-smalltalk.addClass('TestCase', smalltalk.Object, ['testSelector'], 'SUnit');
+smalltalk.addClass('TestCase', smalltalk.Object, ['testSelector', 'asyncTimeout', 'context'], 'SUnit');
 smalltalk.addMethod(
 "_assert_",
 smalltalk.method({
@@ -211,6 +230,45 @@ referencedClasses: []
 }),
 smalltalk.TestCase);
 
+smalltalk.addMethod(
+"_async_",
+smalltalk.method({
+selector: "async:",
+category: 'async',
+fn: function (aBlock){
+var self=this;
+var $1;
+var c;
+smalltalk.send(self,"_mustBeAsync_",["#async"]);
+c=self["@context"];
+$1=(function(){
+return smalltalk.send(c,"_execute_",[aBlock]);
+});
+return $1;
+},
+args: ["aBlock"],
+source: "async: aBlock\x0a\x09| c |\x0a\x09self mustBeAsync: '#async'.\x0a    c := context.\x0a    ^[ c execute: aBlock ]",
+messageSends: ["mustBeAsync:", "execute:"],
+referencedClasses: []
+}),
+smalltalk.TestCase);
+
+smalltalk.addMethod(
+"_context_",
+smalltalk.method({
+selector: "context:",
+category: 'accessing',
+fn: function (aRunningTestContext){
+var self=this;
+self["@context"]=aRunningTestContext;
+return self},
+args: ["aRunningTestContext"],
+source: "context: aRunningTestContext\x0a\x09context := aRunningTestContext",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.TestCase);
+
 smalltalk.addMethod(
 "_deny_",
 smalltalk.method({
@@ -228,6 +286,77 @@ referencedClasses: []
 }),
 smalltalk.TestCase);
 
+smalltalk.addMethod(
+"_finished",
+smalltalk.method({
+selector: "finished",
+category: 'async',
+fn: function (){
+var self=this;
+smalltalk.send(self,"_mustBeAsync_",["#finished"]);
+self["@asyncTimeout"]=nil;
+return self},
+args: [],
+source: "finished\x0a\x09self mustBeAsync: '#finished'.\x0a\x09asyncTimeout := nil",
+messageSends: ["mustBeAsync:"],
+referencedClasses: []
+}),
+smalltalk.TestCase);
+
+smalltalk.addMethod(
+"_graceTime_",
+smalltalk.method({
+selector: "graceTime:",
+category: 'async',
+fn: function (millis){
+var self=this;
+self["@asyncTimeout"]=true;
+return self},
+args: ["millis"],
+source: "graceTime: millis\x0a\x09asyncTimeout := true",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.TestCase);
+
+smalltalk.addMethod(
+"_isAsync",
+smalltalk.method({
+selector: "isAsync",
+category: 'async',
+fn: function (){
+var self=this;
+var $1;
+$1=smalltalk.send(self["@asyncTimeout"],"_notNil",[]);
+return $1;
+},
+args: [],
+source: "isAsync\x0a\x09^asyncTimeout notNil",
+messageSends: ["notNil"],
+referencedClasses: []
+}),
+smalltalk.TestCase);
+
+smalltalk.addMethod(
+"_mustBeAsync_",
+smalltalk.method({
+selector: "mustBeAsync:",
+category: 'async',
+fn: function (aString){
+var self=this;
+var $1;
+$1=smalltalk.send(self,"_isAsync",[]);
+if(! smalltalk.assert($1)){
+smalltalk.send(self,"_error_",[smalltalk.send(aString,"__comma",[" used without prior #graceTime:"])]);
+};
+return self},
+args: ["aString"],
+source: "mustBeAsync: aString\x0a\x09self isAsync ifFalse: [ self error: aString, ' used without prior #graceTime:' ]",
+messageSends: ["ifFalse:", "error:", ",", "isAsync"],
+referencedClasses: []
+}),
+smalltalk.TestCase);
+
 smalltalk.addMethod(
 "_performTest",
 smalltalk.method({
@@ -235,10 +364,11 @@ selector: "performTest",
 category: 'running',
 fn: function (){
 var self=this;
+self["@asyncTimeout"]=nil;
 smalltalk.send(self,"_perform_",[smalltalk.send(self,"_selector",[])]);
 return self},
 args: [],
-source: "performTest\x0a\x09self perform: self selector\x0a",
+source: "performTest\x0a\x09asyncTimeout := nil.\x0a\x09self perform: self selector\x0a",
 messageSends: ["perform:", "selector"],
 referencedClasses: []
 }),

+ 2 - 1
js/amber.js

@@ -97,7 +97,8 @@ amber = (function() {
 				'IDE',
 				'Examples',
 				'Benchfib',
-				'Kernel-Tests'
+				'Kernel-Tests',
+                'SUnit-Tests'
 			]);
 		}
 

+ 74 - 0
st/SUnit-Tests.st

@@ -0,0 +1,74 @@
+Smalltalk current createPackage: 'SUnit-Tests' properties: #{}!
+TestCase subclass: #SUnitAsyncTest
+	instanceVariableNames: 'flag'
+	package: 'SUnit-Tests'!
+
+!SUnitAsyncTest methodsFor: 'running'!
+
+setUp
+	flag := 'ok'
+!
+
+tearDown
+	self assert: 'ok' equals: flag
+! !
+
+!SUnitAsyncTest methodsFor: 'tests'!
+
+fakeError
+	flag := 'bad'.
+	self graceTime: 10.
+    flag := (self async: [ self error: 'Intentional'. self finished. flag := 'ok' ]) valueWithTimeout: 5
+!
+
+fakeFailure
+	flag := 'bad'.
+	self graceTime: 10.
+    flag := (self async: [ self assert: false. self finished. flag := 'ok' ]) valueWithTimeout: 5
+!
+
+testAsyncErrorsAndFailuresWork
+	| suite runner result assertBlock |
+	suite := { self class selector: 'fakeError'. self class selector: 'fakeFailure'. self class selector: 'testPass' }.
+    runner := TestSuiteRunner on: suite.
+    self graceTime: 200.
+	result := runner result.
+    assertBlock := self async: [
+		self assert: 1 equals: result errors size.
+		self assert: 'fakeError' equals: result errors first selector.
+		self assert: 1 equals: result failures size.
+		self assert: 'fakeFailure' equals: result failures first selector.
+		self finished
+  	].
+    runner announcer on: ResultAnnouncement do: [:ann |
+    	ann result == result  ifTrue: [ result runs = result total ifTrue: assertBlock ]].
+	runner run
+!
+
+testAsyncNeedsGraceTime
+    self should: [ self async: [ ] ] raise: Error.
+    self graceTime: 0.
+    self shouldnt: [ self async: [ ] ] raise: Error.
+    self finished
+!
+
+testFinishedNeedsGraceTime
+    self should: [ self finished ] raise: Error.
+    self graceTime: 0.
+    self shouldnt: [ self finished ] raise: Error.
+!
+
+testIsAsyncReturnsCorrectValues
+    self deny: self isAsync.
+    self graceTime: 0.
+    self assert: self isAsync.
+    self finished.
+    self deny: self isAsync
+!
+
+testPass
+	flag := 'bad'.
+	self graceTime: 10.
+    flag := (self async: [ self assert: true. self finished. flag := 'ok' ]) valueWithTimeout: 5
+! !
+

+ 40 - 5
st/SUnit.st

@@ -34,11 +34,15 @@ testCase: aTestCase
 !RunningTestContext methodsFor: 'running'!
 
 execute: aBlock
+    testCase context: self.
 	[[[	aBlock
-        	ensure: [ result increaseRuns. testCase tearDown ]]
-			on: TestFailure do: [:ex | result addFailure: testCase]]
-			on: Error do: [:ex | result addError: testCase]]
-			ensure: finished
+        	ensure: [ testCase context: nil.
+				testCase isAsync ifFalse: [ testCase tearDown ]]]
+			on: TestFailure do: [:ex | testCase isAsync ifTrue: [ testCase finished ]. result addFailure: testCase]]
+			on: Error do: [:ex | testCase isAsync ifTrue: [ testCase finished ]. result addError: testCase]]
+			ensure: [ testCase isAsync ifFalse: [
+                result increaseRuns.
+                finished value ]]
 !
 
 start
@@ -56,11 +60,15 @@ testCase: aTestCase result: aTestResult finished: aBlock
 ! !
 
 Object subclass: #TestCase
-	instanceVariableNames: 'testSelector'
+	instanceVariableNames: 'testSelector asyncTimeout context'
 	package: 'SUnit'!
 
 !TestCase methodsFor: 'accessing'!
 
+context: aRunningTestContext
+	context := aRunningTestContext
+!
+
 selector
 	^testSelector
 !
@@ -69,6 +77,32 @@ setTestSelector: aSelector
 	testSelector := aSelector
 ! !
 
+!TestCase methodsFor: 'async'!
+
+async: aBlock
+	| c |
+	self mustBeAsync: '#async'.
+    c := context.
+    ^[ c execute: aBlock ]
+!
+
+finished
+	self mustBeAsync: '#finished'.
+	asyncTimeout := nil
+!
+
+graceTime: millis
+	asyncTimeout := true
+!
+
+isAsync
+	^asyncTimeout notNil
+!
+
+mustBeAsync: aString
+	self isAsync ifFalse: [ self error: aString, ' used without prior #graceTime:' ]
+! !
+
 !TestCase methodsFor: 'private'!
 
 signalFailure: aString
@@ -80,6 +114,7 @@ signalFailure: aString
 !TestCase methodsFor: 'running'!
 
 performTest
+	asyncTimeout := nil.
 	self perform: self selector
 !
 

+ 1 - 1
test/run_build.sh

@@ -3,7 +3,7 @@ cd `dirname ${0}`/..
 
 
 # Create the build file
-cat js/boot.js js/Kernel-Announcements.js js/Kernel-Classes.js js/Kernel-Collections.js  js/Kernel-Exceptions.js js/Kernel-Methods.js js/Kernel-Objects.js js/Kernel-Transcript.js js/Compiler-AST.js js/Compiler-Core.js js/Compiler-Exceptions.js js/Compiler-IR.js js/Compiler-Inlining.js js/Compiler-Semantic.js js/SUnit.js js/Kernel-Tests.js js/Compiler-Tests.js test/Test.js js/parser.js js/init.js > test/run.js
+cat js/boot.js js/Kernel-Announcements.js js/Kernel-Classes.js js/Kernel-Collections.js  js/Kernel-Exceptions.js js/Kernel-Methods.js js/Kernel-Objects.js js/Kernel-Transcript.js js/Compiler-AST.js js/Compiler-Core.js js/Compiler-Exceptions.js js/Compiler-IR.js js/Compiler-Inlining.js js/Compiler-Semantic.js js/SUnit.js js/Kernel-Tests.js js/Compiler-Tests.js js/SUnit-Tests.js test/Test.js js/parser.js js/init.js > test/run.js
 
 
 #run it!