Home | History | Annotate | Download | only in lib
      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 function 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 
     53 var context = {
     54   beingDescribed: undefined,
     55   currentSuiteIndex: 0,
     56   suites: []
     57 };
     58 
     59 function 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 
     79 RunAllTests = 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 
     90 function 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 
     99 TestCase.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 
    118 TestCase.prototype.MarkAsDone = function() {
    119   this.hasDone = true;
    120   clearTimeout(this.timer);
    121 }
    122 
    123 TestCase.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 
    171 function 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 
    194 TestSuite.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 
    208 TestSuite.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 
    218 TestSuite.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 
    230 describe = 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 
    244 specify = it = function(description, fn) {
    245   context.beingDescribed.cases.push({description: description, fn: fn});
    246 }
    247 
    248 before = function(fn) {
    249   context.beingDescribed.before = fn;
    250 }
    251 
    252 after = function(fn) {
    253   context.beingDescribed.after = fn;
    254 }
    255 
    256 beforeEach = function(fn) {
    257   context.beingDescribed.beforeEach = fn;
    258 }
    259 
    260 afterEach = function(fn) {
    261   context.beingDescribed.afterEach = fn;
    262 }
    263 
    264 }());
    265