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 30var describe; 31var it; 32var specify; 33var before; 34var after; 35var beforeEach; 36var afterEach; 37var RunAllTests; 38 39var assert = require('assert'); 40 41(function() { 42var TIMEOUT = 1000; 43 44function PostMicrotask(fn) { 45 var o = {}; 46 Object.observe(o, function() { 47 fn(); 48 }); 49 // Change something to enqueue a microtask. 50 o.x = 'hello'; 51} 52 53var context = { 54 beingDescribed: undefined, 55 currentSuiteIndex: 0, 56 suites: [] 57}; 58 59function Run() { 60 function current() { 61 while (context.currentSuiteIndex < context.suites.length && 62 context.suites[context.currentSuiteIndex].hasRun) { 63 ++context.currentSuiteIndex; 64 } 65 if (context.suites.length == context.currentSuiteIndex) { 66 return undefined; 67 } 68 return context.suites[context.currentSuiteIndex]; 69 } 70 var suite = current(); 71 if (!suite) { 72 // done 73 print('All tests have run.'); 74 return; 75 } 76 suite.Run(); 77} 78 79RunAllTests = function() { 80 context.currentSuiteIndex = 0; 81 var numRegularTestCases = 0; 82 for (var i = 0; i < context.suites.length; ++i) { 83 numRegularTestCases += context.suites[i].numRegularTestCases(); 84 } 85 print(context.suites.length + ' suites and ' + numRegularTestCases + 86 ' test cases are found'); 87 Run(); 88}; 89 90function TestCase(name, before, fn, after, isRegular) { 91 this.name = name; 92 this.before = before; 93 this.fn = fn; 94 this.after = after; 95 this.isRegular = isRegular; 96 this.hasDone = false; 97} 98 99TestCase.prototype.RunFunction = function(suite, fn, postAction) { 100 if (!fn) { 101 postAction(); 102 return; 103 } 104 try { 105 if (fn.length === 0) { 106 // synchronous 107 fn(); 108 postAction(); 109 } else { 110 // asynchronous 111 fn(postAction); 112 } 113 } catch (e) { 114 suite.ReportError(this, e); 115 } 116} 117 118TestCase.prototype.MarkAsDone = function() { 119 this.hasDone = true; 120 clearTimeout(this.timer); 121} 122 123TestCase.prototype.Run = function(suite, postAction) { 124 print('Running ' + suite.description + '#' + this.name + ' ...'); 125 assert.clear(); 126 127 this.timer = setTimeout(function() { 128 suite.ReportError(this, Error('timeout')); 129 }.bind(this), TIMEOUT); 130 131 this.RunFunction(suite, this.before, function(e) { 132 if (this.hasDone) { 133 return; 134 } 135 if (e instanceof Error) { 136 return suite.ReportError(this, e); 137 } 138 if (assert.fails.length > 0) { 139 return suite.ReportError(this, assert.fails[0]); 140 } 141 this.RunFunction(suite, this.fn, function(e) { 142 if (this.hasDone) { 143 return; 144 } 145 if (e instanceof Error) { 146 return suite.ReportError(this, e); 147 } 148 if (assert.fails.length > 0) { 149 return suite.ReportError(this, assert.fails[0]); 150 } 151 this.RunFunction(suite, this.after, function(e) { 152 if (this.hasDone) { 153 return; 154 } 155 if (e instanceof Error) { 156 return suite.ReportError(this, e); 157 } 158 if (assert.fails.length > 0) { 159 return suite.ReportError(this, assert.fails[0]); 160 } 161 this.MarkAsDone(); 162 if (this.isRegular) { 163 print('PASS: ' + suite.description + '#' + this.name); 164 } 165 PostMicrotask(postAction); 166 }.bind(this)); 167 }.bind(this)); 168 }.bind(this)); 169}; 170 171function TestSuite(described) { 172 this.description = described.description; 173 this.cases = []; 174 this.currentIndex = 0; 175 this.hasRun = false; 176 177 if (described.before) { 178 this.cases.push(new TestCase(this.description + ' :before', undefined, 179 described.before, undefined, false)); 180 } 181 for (var i = 0; i < described.cases.length; ++i) { 182 this.cases.push(new TestCase(described.cases[i].description, 183 described.beforeEach, 184 described.cases[i].fn, 185 described.afterEach, 186 true)); 187 } 188 if (described.after) { 189 this.cases.push(new TestCase(this.description + ' :after', 190 undefined, described.after, undefined, false)); 191 } 192} 193 194TestSuite.prototype.Run = function() { 195 this.hasRun = this.currentIndex === this.cases.length; 196 if (this.hasRun) { 197 PostMicrotask(Run); 198 return; 199 } 200 201 // TestCase.prototype.Run cannot throw an exception. 202 this.cases[this.currentIndex].Run(this, function() { 203 ++this.currentIndex; 204 PostMicrotask(Run); 205 }.bind(this)); 206}; 207 208TestSuite.prototype.numRegularTestCases = function() { 209 var n = 0; 210 for (var i = 0; i < this.cases.length; ++i) { 211 if (this.cases[i].isRegular) { 212 ++n; 213 } 214 } 215 return n; 216} 217 218TestSuite.prototype.ReportError = function(testCase, e) { 219 if (testCase.hasDone) { 220 return; 221 } 222 testCase.MarkAsDone(); 223 this.hasRun = this.currentIndex === this.cases.length; 224 print('FAIL: ' + this.description + '#' + testCase.name + ': ' + 225 e.name + ' (' + e.message + ')'); 226 ++this.currentIndex; 227 PostMicrotask(Run); 228}; 229 230describe = function(description, fn) { 231 var parent = context.beingDescribed; 232 var incomplete = { 233 cases: [], 234 description: parent ? parent.description + ' ' + description : description, 235 parent: parent, 236 }; 237 context.beingDescribed = incomplete; 238 fn(); 239 context.beingDescribed = parent; 240 241 context.suites.push(new TestSuite(incomplete)); 242} 243 244specify = it = function(description, fn) { 245 context.beingDescribed.cases.push({description: description, fn: fn}); 246} 247 248before = function(fn) { 249 context.beingDescribed.before = fn; 250} 251 252after = function(fn) { 253 context.beingDescribed.after = fn; 254} 255 256beforeEach = function(fn) { 257 context.beingDescribed.beforeEach = fn; 258} 259 260afterEach = function(fn) { 261 context.beingDescribed.afterEach = fn; 262} 263 264}()); 265