Skip to content

Commit

Permalink
suite.js init
Browse files Browse the repository at this point in the history
  • Loading branch information
cloudhead committed Jun 3, 2010
1 parent b9c0329 commit 6fe14ec
Showing 1 changed file with 197 additions and 0 deletions.
197 changes: 197 additions & 0 deletions lib/vows/suite.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
var events = require('events'),
path = require('path');

var vows = require(path.join(__dirname, '..', 'vows'));
var Context = require('vows/context').Context;
var tryEnd = vows.tryEnd;

this.Suite = function () {
this.results = {
honored: 0,
broken: 0,
errored: 0,
total: 0,
time: null
};
this.batches = [];
};

this.Suite.prototype = new(function () {
this.addVows = function (tests) {
var batch = {
tests: tests,
suite: this,
remaining: 0,
honored: 0,
broken: 0,
errored: 0,
total: 0
};

this.batches.push(batch);

if (typeof(tests) === 'object') {
if ('topic' in tests) {
throw new(Error)("missing top-level context.");
}
// Count the number of vows/promises expected to fire,
// so we know when the tests are over.
// We match the keys against `matcher`, to decide
// whether or not they should be included in the test.
(function count(tests) {
var match = false;
batch.remaining ++;
Object.keys(tests).filter(function (k) {
return k !== 'topic';
}).forEach(function (key) {
if (typeof(tests[key]) === "object") {
if (! (match = count(tests[key]) ||
match || vows.options.matcher.test(key))) {
delete tests[key];
batch.remaining --;
}
}
});
return match;
})(tests);
}

return this;
};

this.runVows = function (batch) {
var topic,
tests = batch.tests,
promise = batch.promise = new(events.EventEmitter);

if (typeof(tests) === 'function') {
return tests.call(null);
} else {
// The test runner, it calls itself recursively, passing the
// previous context to the inner contexts. This is so the `topic`
// functions have access to all the previous context topics in their
// arguments list.
// It is defined and invoked at the same time.
// If it encounters a `topic` function, it waits for the returned
// promise to emit (the topic), at which point it runs the functions under it,
// passing the topic as an argument.
(function run(ctx, lastTopic) {
var old = false;
topic = ctx.tests.topic;

if (typeof(topic) === 'function') {
// Run the topic, passing the previous context topics
topic = topic.apply(ctx.env, ctx.topics);
}

// If this context has a topic, store it in `lastTopic`,
// if not, use the last topic, passed down by a parent
// context.
if (topic) {
lastTopic = topic;
} else {
old = true;
topic = lastTopic;
}

// If the topic doesn't return an event emitter (such as a promise),
// we create it ourselves, and emit the value on the next tick.
if (! (topic instanceof vows.options.Emitter)) {
ctx.emitter = new(vows.options.Emitter);

if (! ctx._callback) {
process.nextTick(function (val) {
return function () { ctx.emitter.emit("success", val) };
}(topic));
} else if (typeof(topic) !== "undefined" && !old) {
throw new(Error)("topic must not return anything when using `this.callback`.");
}
topic = ctx.emitter;
}

topic.addListener('success', function (val) {
// Once the topic fires, add the return value
// to the beginning of the topics list, so it
// becomes the first argument for the next topic.
// If we're using the parent topic, no need to
// prepend it to the topics list, or we'll get
// duplicates.
if (! old) ctx.topics.unshift(val);
});

// Now run the tests, or sub-contexts
Object.keys(ctx.tests).filter(function (k) {
return ctx.tests[k] && k !== 'topic';
}).forEach(function (item) {
// Create a new evaluation context,
// inheriting from the parent one.
var env = Object.create(ctx.env);

// Holds the current test or context
var vow = Object.create({
callback: ctx.tests[item],
context: ctx.name,
description: item,
binding: ctx.env,
batch: batch
});

// If we encounter a function, add it to the callbacks
// of the `topic` function, so it'll get called once the
// topic fires.
// If we encounter an object literal, we recurse, sending it
// our current context.
if (typeof(vow.callback) === 'function') {
topic.addVow(vow);
} else if (typeof(vow.callback) === 'object' && ! Array.isArray(vow.callback)) {
// If there's a setup stage, we have to wait for it to fire,
// before calling the inner context. Else, just run the inner context
// synchronously.
if (topic) {
topic.addListener("success", function (ctx) {
return function (val) {
return run(new(Context)(vow, ctx, env), lastTopic);
};
}(ctx));
} else {
run(new(Context)(vow, ctx, env), lastTopic);
}
}
});
// Check if we're done running the tests
batch.remaining --;
tryEnd(batch);
// This is our initial, empty context
})(new(Context)({ callback: tests, context: null, description: null }, {}));
}
return promise;
};

this.run = function (callback) {
var that = this;
var start = new(Date);

(function run(batches) {
var batch;

if (batch = batches.shift()) {
that.runVows(batch).addListener('end', function () {
run(batches);
});
} else {
that.results.time = (new(Date) - start) / 1000;

vows.reporter.report(['finish', that.results]);

if (callback) { callback(that.results) }

// Don't exit until stdout is empty
process.stdout.addListener('drain', function () {
process.exit(that.results.broken || that.results.errored ? 1 : 0);
});
}
})(this.batches.slice(0));
};

this.runParallel = function () {};
});

0 comments on commit 6fe14ec

Please sign in to comment.