UNPKG

compound-ex4

Version:

Compound-ex4 - MVC framework for NodeJS (ExpressJs 4 version), fork compoundjs(https://github.com/1602/compound)

247 lines (217 loc) 6.98 kB
module.exports = TapConsumer // pipe a stream into this that's emitting tap-formatted data, // and it'll emit "data" events with test objects or comment strings // and an "end" event with the final results. var yamlish = require("yamlish") , Results = require("./tap-results") , inherits = require("inherits") TapConsumer.decode = TapConsumer.parse = function (str) { var tc = new TapConsumer , list = [] tc.on("data", function (res) { list.push(res) }) tc.end(str) tc.results.list = list return tc.results } var Stream = require("stream").Stream inherits(TapConsumer, Stream) function TapConsumer () { if (!(this instanceof TapConsumer)) { return new TapConsumer } Stream.call(this) this.results = new Results this.readable = this.writable = true this.on("data", function (res) { if (typeof res === "object") this.results.add(res) }) this._plan = null this._buffer = "" this._indent = [] this._current = null this._actualCount = 0 this._passed = [] this._failed = [] //console.error("TapConsumer ctor done") } TapConsumer.prototype.bailedOut = false TapConsumer.prototype.write = function (chunk) { if (!this.writable) this.emit("error", new Error("not writable")) if (this.bailedOut) return true this._buffer = this._buffer + chunk // split it up into lines. var lines = this._buffer.split(/\r?\n/) // ignore the last line, since it might be incomplete. this._buffer = lines.pop() for (var i = 0, l = lines.length; i < l; i ++) { //console.error([i, lines[i]]) // see if it's indented. var line = lines[i] , spaces = (this._indent.length && !line.trim()) || line.match(/^\s/) // at this level, only interested in fully undented stuff. if (spaces) { var c = i while (c < l && (!lines[c].trim() || lines[c].match(/^\s/))) { this._indent.push(lines[c++]) } //console.error(c-i, "indented", this._indent, this._current) i = c - 1 continue } // some kind of line. summary, ok, notok, comment, or garbage. // this also finishes parsing any of the indented lines from before this._parseLine(line) } return true } TapConsumer.prototype.end = function () { // finish up any hanging indented sections or final buffer if (this._buffer.match(/^\s/)) this._indent.push(this.buffer) else this._parseLine(this._buffer) if (!this.bailedOut && this._plan !== null && this.results.testsTotal !== this._plan) { while (this._actualCount < this._plan) { this.emit("data", {ok: false, name:"MISSING TEST", id:this._actualCount ++ }) } } this._parseLine("") this._buffer = "" this.writable = false this.emit("end", null, this._actualCount, this._passed) } TapConsumer.prototype._parseLine = function (line) { if (this.bailedOut) return //console.error("_parseLine", [line]) // if there are any indented lines, and there is a // current object already, then they belong to it. // if there is not a current object, then they're garbage. if (this._current && this._indent.length) { this._parseIndented() } this._indent.length = 0 if (this._current) { if (this._current.ok) this._passed.push(this._current.id) else this._failed.push(this._current.id) this.emit("data", this._current) } this._current = null line = line.trim() if (!line) return // try to see what kind of line this is. var bo if (bo = line.match(/^bail out!\s*(.*)$/i)) { this.bailedOut = true // this.emit("error", new Error(line)) this.emit("bailout", bo[1]) return } if (line.match(/^#/)) { // just a comment line = line.replace(/^#+/, "").trim() // console.error("outputting comment", [line]) if (line) this.emit("data", line) return } var plan = line.match(/^([0-9]+)\.\.([0-9]+)(?:\s+#(.*))?$/) if (plan) { var start = +(plan[1]) , end = +(plan[2]) , comment = plan[3] // TODO: maybe do something else with this? // it might be something like: "1..0 #Skip because of reasons" this._plan = end this.emit("plan", end, comment) // plan must come before or after all tests. if (this._actualCount !== 0) { this._sawPlan = true } return } if (line.match(/^(not )?ok(?:\s+([0-9]+))?/)) { this._parseResultLine(line) return } // garbage. emit as a comment. //console.error("emitting", [line.trim()]) if (line.trim()) this.emit("data", line.trim()) } TapConsumer.prototype._parseDirective = function (line) { line = line.trim() if (line.match(/^TODO\b/i)) { return { todo:true, explanation: line.replace(/^TODO\s*/i, "") } } else if (line.match(/^SKIP\b/i)) { return { skip:true, explanation: line.replace(/^SKIP\s*/i, "") } } } TapConsumer.prototype._parseResultLine = function (line) { this._actualCount ++ if (this._sawPlan) { this.emit("data", {ok: false, name:"plan in the middle of tests" ,id:this._actualCount ++}) } var parsed = line.match(/^(not )?ok(?: ([0-9]+))?(?:(?: - )?(.*))?$/) , ok = !parsed[1] , id = +(parsed[2] || this._actualCount) , rest = parsed[3] || "" , name , res = { id:id, ok:ok } // split on un-escaped # characters //console.log("# "+JSON.stringify([name, rest])) rest = rest.replace(/([^\\])((?:\\\\)*)#/g, "$1\n$2").split("\n") name = rest.shift() rest = rest.filter(function (r) { return r.trim() }).join("#") //console.log("# "+JSON.stringify([name, rest])) // now, let's see if there's a directive in there. var dir = this._parseDirective(rest.trim()) if (!dir) name += rest ? "#" + rest : "" else { res.ok = true if (dir.skip) res.skip = true else if (dir.todo) res.todo = true if (dir.explanation) res.explanation = dir.explanation } res.name = name //console.error(line, [ok, id, name]) this._current = res } TapConsumer.prototype._parseIndented = function () { // pull yamlish block out var ind = this._indent , ys , ye , yind , diag //console.error(ind, this._indent) for (var i = 0, l = ind.length; i < l; i ++) { var line = ind[i] if (line === undefined) continue var lt = line.trim() if (!ys) { ys = line.match(/^(\s*)---(.*)$/) if (ys) { yind = ys[1] diag = [ys[2]] //console.error([line,ys, diag]) continue } else if (lt) this.emit("data", lt) } else if (ys && !ye) { if (line === yind + "...") ye = true else { diag.push(line.substr(yind.length)) } } else if (ys && ye && lt) this.emit("data", lt) } if (diag) { //console.error('about to parse', diag) diag = yamlish.decode(diag.join("\n")) //console.error('parsed', diag) Object.keys(diag).forEach(function (k) { //console.error(this._current, k) if (!this._current.hasOwnProperty(k)) this._current[k] = diag[k] }, this) } }