1 // Copyright 2014 the V8 project authors. All rights reserved. 2 // Redistribution and use in source and binary forms, with or without 3 // modification, are permitted provided that the following conditions are 4 // met: 5 // 6 // * Redistributions of source code must retain the above copyright 7 // notice, this list of conditions and the following disclaimer. 8 // * Redistributions in binary form must reproduce the above 9 // copyright notice, this list of conditions and the following 10 // disclaimer in the documentation and/or other materials provided 11 // with the distribution. 12 // * Neither the name of Google Inc. nor the names of its 13 // contributors may be used to endorse or promote products derived 14 // from this software without specific prior written permission. 15 // 16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 28 // This file emulates Mocha test framework used in promises-aplus tests. 29 30 var describe; 31 var it; 32 var specify; 33 var before; 34 var after; 35 var beforeEach; 36 var afterEach; 37 var RunAllTests; 38 39 var assert = require('assert'); 40 41 (function() { 42 var TIMEOUT = 1000; 43 44 var context = { 45 beingDescribed: undefined, 46 currentSuiteIndex: 0, 47 suites: [] 48 }; 49 50 function Run() { 51 function current() { 52 while (context.currentSuiteIndex < context.suites.length && 53 context.suites[context.currentSuiteIndex].hasRun) { 54 ++context.currentSuiteIndex; 55 } 56 if (context.suites.length == context.currentSuiteIndex) { 57 return undefined; 58 } 59 return context.suites[context.currentSuiteIndex]; 60 } 61 var suite = current(); 62 if (!suite) { 63 // done 64 print('All tests have run.'); 65 return; 66 } 67 suite.Run(); 68 } 69 70 RunAllTests = function() { 71 context.currentSuiteIndex = 0; 72 var numRegularTestCases = 0; 73 for (var i = 0; i < context.suites.length; ++i) { 74 numRegularTestCases += context.suites[i].numRegularTestCases(); 75 } 76 print(context.suites.length + ' suites and ' + numRegularTestCases + 77 ' test cases are found'); 78 Run(); 79 }; 80 81 function TestCase(name, before, fn, after, isRegular) { 82 this.name = name; 83 this.before = before; 84 this.fn = fn; 85 this.after = after; 86 this.isRegular = isRegular; 87 this.hasDone = false; 88 } 89 90 TestCase.prototype.RunFunction = function(suite, fn, postAction) { 91 if (!fn) { 92 postAction(); 93 return; 94 } 95 try { 96 if (fn.length === 0) { 97 // synchronous 98 fn(); 99 postAction(); 100 } else { 101 // asynchronous 102 fn(postAction); 103 } 104 } catch (e) { 105 suite.ReportError(this, e); 106 } 107 } 108 109 TestCase.prototype.MarkAsDone = function() { 110 this.hasDone = true; 111 clearTimeout(this.timer); 112 } 113 114 TestCase.prototype.Run = function(suite, postAction) { 115 print('Running ' + suite.description + '#' + this.name + ' ...'); 116 assert.clear(); 117 118 this.timer = setTimeout(function() { 119 suite.ReportError(this, Error('timeout')); 120 }.bind(this), TIMEOUT); 121 122 this.RunFunction(suite, this.before, function(e) { 123 if (this.hasDone) { 124 return; 125 } 126 if (e instanceof Error) { 127 return suite.ReportError(this, e); 128 } 129 if (assert.fails.length > 0) { 130 return suite.ReportError(this, assert.fails[0]); 131 } 132 this.RunFunction(suite, this.fn, function(e) { 133 if (this.hasDone) { 134 return; 135 } 136 if (e instanceof Error) { 137 return suite.ReportError(this, e); 138 } 139 if (assert.fails.length > 0) { 140 return suite.ReportError(this, assert.fails[0]); 141 } 142 this.RunFunction(suite, this.after, function(e) { 143 if (this.hasDone) { 144 return; 145 } 146 if (e instanceof Error) { 147 return suite.ReportError(this, e); 148 } 149 if (assert.fails.length > 0) { 150 return suite.ReportError(this, assert.fails[0]); 151 } 152 this.MarkAsDone(); 153 if (this.isRegular) { 154 print('PASS: ' + suite.description + '#' + this.name); 155 } 156 %EnqueueMicrotask(postAction); 157 }.bind(this)); 158 }.bind(this)); 159 }.bind(this)); 160 }; 161 162 function TestSuite(described) { 163 this.description = described.description; 164 this.cases = []; 165 this.currentIndex = 0; 166 this.hasRun = false; 167 168 if (described.before) { 169 this.cases.push(new TestCase(this.description + ' :before', undefined, 170 described.before, undefined, false)); 171 } 172 for (var i = 0; i < described.cases.length; ++i) { 173 this.cases.push(new TestCase(described.cases[i].description, 174 described.beforeEach, 175 described.cases[i].fn, 176 described.afterEach, 177 true)); 178 } 179 if (described.after) { 180 this.cases.push(new TestCase(this.description + ' :after', 181 undefined, described.after, undefined, false)); 182 } 183 } 184 185 TestSuite.prototype.Run = function() { 186 this.hasRun = this.currentIndex === this.cases.length; 187 if (this.hasRun) { 188 %EnqueueMicrotask(Run); 189 return; 190 } 191 192 // TestCase.prototype.Run cannot throw an exception. 193 this.cases[this.currentIndex].Run(this, function() { 194 ++this.currentIndex; 195 %EnqueueMicrotask(Run); 196 }.bind(this)); 197 }; 198 199 TestSuite.prototype.numRegularTestCases = function() { 200 var n = 0; 201 for (var i = 0; i < this.cases.length; ++i) { 202 if (this.cases[i].isRegular) { 203 ++n; 204 } 205 } 206 return n; 207 } 208 209 TestSuite.prototype.ReportError = function(testCase, e) { 210 if (testCase.hasDone) { 211 return; 212 } 213 testCase.MarkAsDone(); 214 this.hasRun = this.currentIndex === this.cases.length; 215 print('FAIL: ' + this.description + '#' + testCase.name + ': ' + 216 e.name + ' (' + e.message + ')'); 217 ++this.currentIndex; 218 %EnqueueMicrotask(Run); 219 }; 220 221 describe = function(description, fn) { 222 var parent = context.beingDescribed; 223 var incomplete = { 224 cases: [], 225 description: parent ? parent.description + ' ' + description : description, 226 parent: parent, 227 }; 228 context.beingDescribed = incomplete; 229 fn(); 230 context.beingDescribed = parent; 231 232 context.suites.push(new TestSuite(incomplete)); 233 } 234 235 specify = it = function(description, fn) { 236 context.beingDescribed.cases.push({description: description, fn: fn}); 237 } 238 239 before = function(fn) { 240 context.beingDescribed.before = fn; 241 } 242 243 after = function(fn) { 244 context.beingDescribed.after = fn; 245 } 246 247 beforeEach = function(fn) { 248 context.beingDescribed.beforeEach = fn; 249 } 250 251 afterEach = function(fn) { 252 context.beingDescribed.afterEach = fn; 253 } 254 255 }()); 256