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
44var context = {
45  beingDescribed: undefined,
46  currentSuiteIndex: 0,
47  suites: []
48};
49
50function 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
70RunAllTests = 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
81function 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
90TestCase.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
109TestCase.prototype.MarkAsDone = function() {
110  this.hasDone = true;
111  clearTimeout(this.timer);
112}
113
114TestCase.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
162function 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
185TestSuite.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
199TestSuite.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
209TestSuite.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
221describe = 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
235specify = it = function(description, fn) {
236  context.beingDescribed.cases.push({description: description, fn: fn});
237}
238
239before = function(fn) {
240  context.beingDescribed.before = fn;
241}
242
243after = function(fn) {
244  context.beingDescribed.after = fn;
245}
246
247beforeEach = function(fn) {
248  context.beingDescribed.beforeEach = fn;
249}
250
251afterEach = function(fn) {
252  context.beingDescribed.afterEach = fn;
253}
254
255}());
256