Name: js-handler/node_modules/nodeunit/node_modules/tap/lib/tap-consumer.js
| 1: | module.exports = TapConsumer |
| 2: | |
| 3: | // pipe a stream into this that's emitting tap-formatted data, |
| 4: | // and it'll emit "data" events with test objects or comment strings |
| 5: | // and an "end" event with the final results. |
| 6: | |
| 7: | var yamlish = require("yamlish") |
| 8: | , Results = require("./tap-results") |
| 9: | , inherits = require("inherits") |
| 10: | |
| 11: | TapConsumer.decode = TapConsumer.parse = function (str) { |
| 12: | var tc = new TapConsumer |
| 13: | , list = [] |
| 14: | tc.on("data", function (res) { |
| 15: | list.push(res) |
| 16: | }) |
| 17: | tc.end(str) |
| 18: | tc.results.list = list |
| 19: | return tc.results |
| 20: | } |
| 21: | |
| 22: | var Stream = require("stream").Stream |
| 23: | inherits(TapConsumer, Stream) |
| 24: | function TapConsumer () { |
| 25: | if (!(this instanceof TapConsumer)) { |
| 26: | return new TapConsumer |
| 27: | } |
| 28: | |
| 29: | Stream.call(this) |
| 30: | this.results = new Results |
| 31: | this.readable = this.writable = true |
| 32: | |
| 33: | this.on("data", function (res) { |
| 34: | if (typeof res === "object") this.results.add(res) |
| 35: | }) |
| 36: | |
| 37: | this._plan = null |
| 38: | this._buffer = "" |
| 39: | this._indent = [] |
| 40: | this._current = null |
| 41: | this._actualCount = 0 |
| 42: | this._passed = [] |
| 43: | this._failed = [] |
| 44: | //console.error("TapConsumer ctor done") |
| 45: | } |
| 46: | |
| 47: | TapConsumer.prototype.bailedOut = false |
| 48: | |
| 49: | TapConsumer.prototype.write = function (chunk) { |
| 50: | if (!this.writable) this.emit("error", new Error("not writable")) |
| 51: | if (this.bailedOut) return true |
| 52: | |
| 53: | this._buffer = this._buffer + chunk |
| 54: | // split it up into lines. |
| 55: | var lines = this._buffer.split(/\r?\n/) |
| 56: | // ignore the last line, since it might be incomplete. |
| 57: | this._buffer = lines.pop() |
| 58: | |
| 59: | for (var i = 0, l = lines.length; i < l; i ++) { |
| 60: | //console.error([i, lines[i]]) |
| 61: | // see if it's indented. |
| 62: | var line = lines[i] |
| 63: | , spaces = (this._indent.length && !line.trim()) |
| 64: | || line.match(/^\s/) |
| 65: | // at this level, only interested in fully undented stuff. |
| 66: | if (spaces) { |
| 67: | var c = i |
| 68: | while (c < l && (!lines[c].trim() || lines[c].match(/^\s/))) { |
| 69: | this._indent.push(lines[c++]) |
| 70: | } |
| 71: | //console.error(c-i, "indented", this._indent, this._current) |
| 72: | i = c - 1 |
| 73: | continue |
| 74: | } |
| 75: | // some kind of line. summary, ok, notok, comment, or garbage. |
| 76: | // this also finishes parsing any of the indented lines from before |
| 77: | this._parseLine(line) |
| 78: | } |
| 79: | return true |
| 80: | } |
| 81: | |
| 82: | TapConsumer.prototype.end = function () { |
| 83: | // finish up any hanging indented sections or final buffer |
| 84: | if (this._buffer.match(/^\s/)) this._indent.push(this.buffer) |
| 85: | else this._parseLine(this._buffer) |
| 86: | |
| 87: | if (!this.bailedOut && |
| 88: | this._plan !== null && |
| 89: | this.results.testsTotal !== this._plan) { |
| 90: | while (this._actualCount < this._plan) { |
| 91: | this.emit("data", {ok: false, name:"MISSING TEST", |
| 92: | id:this._actualCount ++ }) |
| 93: | } |
| 94: | } |
| 95: | |
| 96: | this._parseLine("") |
| 97: | this._buffer = "" |
| 98: | this.writable = false |
| 99: | this.emit("end", null, this._actualCount, this._passed) |
| 100: | } |
| 101: | |
| 102: | TapConsumer.prototype._parseLine = function (line) { |
| 103: | if (this.bailedOut) return |
| 104: | //console.error("_parseLine", [line]) |
| 105: | // if there are any indented lines, and there is a |
| 106: | // current object already, then they belong to it. |
| 107: | // if there is not a current object, then they're garbage. |
| 108: | if (this._current && this._indent.length) { |
| 109: | this._parseIndented() |
| 110: | } |
| 111: | this._indent.length = 0 |
| 112: | if (this._current) { |
| 113: | if (this._current.ok) this._passed.push(this._current.id) |
| 114: | else this._failed.push(this._current.id) |
| 115: | this.emit("data", this._current) |
| 116: | } |
| 117: | this._current = null |
| 118: | line = line.trim() |
| 119: | if (!line) return |
| 120: | // try to see what kind of line this is. |
| 121: | |
| 122: | var bo |
| 123: | if (bo = line.match(/^bail out!\s*(.*)$/i)) { |
| 124: | this.bailedOut = true |
| 125: | // this.emit("error", new Error(line)) |
| 126: | this.emit("bailout", bo[1]) |
| 127: | return |
| 128: | } |
| 129: | |
| 130: | if (line.match(/^#/)) { // just a comment |
| 131: | line = line.replace(/^#+/, "").trim() |
| 132: | // console.error("outputting comment", [line]) |
| 133: | if (line) this.emit("data", line) |
| 134: | return |
| 135: | } |
| 136: | |
| 137: | var plan = line.match(/^([0-9]+)\.\.([0-9]+)(?:\s+#(.*))?$/) |
| 138: | if (plan) { |
| 139: | var start = +(plan[1]) |
| 140: | , end = +(plan[2]) |
| 141: | , comment = plan[3] |
| 142: | |
| 143: | // TODO: maybe do something else with this? |
| 144: | // it might be something like: "1..0 #Skip because of reasons" |
| 145: | this._plan = end |
| 146: | this.emit("plan", end, comment) |
| 147: | // plan must come before or after all tests. |
| 148: | if (this._actualCount !== 0) { |
| 149: | this._sawPlan = true |
| 150: | } |
| 151: | return |
| 152: | } |
| 153: | |
| 154: | if (line.match(/^(not )?ok(?:\s+([0-9]+))?/)) { |
| 155: | this._parseResultLine(line) |
| 156: | return |
| 157: | } |
| 158: | |
| 159: | // garbage. emit as a comment. |
| 160: | //console.error("emitting", [line.trim()]) |
| 161: | if (line.trim()) this.emit("data", line.trim()) |
| 162: | } |
| 163: | |
| 164: | TapConsumer.prototype._parseDirective = function (line) { |
| 165: | line = line.trim() |
| 166: | if (line.match(/^TODO\b/i)) { |
| 167: | return { todo:true, explanation: line.replace(/^TODO\s*/i, "") } |
| 168: | } else if (line.match(/^SKIP\b/i)) { |
| 169: | return { skip:true, explanation: line.replace(/^SKIP\s*/i, "") } |
| 170: | } |
| 171: | } |
| 172: | |
| 173: | TapConsumer.prototype._parseResultLine = function (line) { |
| 174: | this._actualCount ++ |
| 175: | if (this._sawPlan) { |
| 176: | this.emit("data", {ok: false, name:"plan in the middle of tests" |
| 177: | ,id:this._actualCount ++}) |
| 178: | } |
| 179: | var parsed = line.match(/^(not )?ok(?: ([0-9]+))?(?:(?: - )?(.*))?$/) |
| 180: | , ok = !parsed[1] |
| 181: | , id = +(parsed[2] || this._actualCount) |
| 182: | , rest = parsed[3] || "" |
| 183: | , name |
| 184: | , res = { id:id, ok:ok } |
| 185: | |
| 186: | // split on un-escaped # characters |
| 187: | |
| 188: | //console.log("# "+JSON.stringify([name, rest])) |
| 189: | rest = rest.replace(/([^\\])((?:\\\\)*)#/g, "$1\n$2").split("\n") |
| 190: | name = rest.shift() |
| 191: | rest = rest.filter(function (r) { return r.trim() }).join("#") |
| 192: | //console.log("# "+JSON.stringify([name, rest])) |
| 193: | |
| 194: | // now, let's see if there's a directive in there. |
| 195: | var dir = this._parseDirective(rest.trim()) |
| 196: | if (!dir) name += rest ? "#" + rest : "" |
| 197: | else { |
| 198: | res.ok = true |
| 199: | if (dir.skip) res.skip = true |
| 200: | else if (dir.todo) res.todo = true |
| 201: | if (dir.explanation) res.explanation = dir.explanation |
| 202: | } |
| 203: | res.name = name |
| 204: | |
| 205: | //console.error(line, [ok, id, name]) |
| 206: | this._current = res |
| 207: | } |
| 208: | |
| 209: | TapConsumer.prototype._parseIndented = function () { |
| 210: | // pull yamlish block out |
| 211: | var ind = this._indent |
| 212: | , ys |
| 213: | , ye |
| 214: | , yind |
| 215: | , diag |
| 216: | //console.error(ind, this._indent) |
| 217: | for (var i = 0, l = ind.length; i < l; i ++) { |
| 218: | var line = ind[i] |
| 219: | if (line === undefined) continue |
| 220: | var lt = line.trim() |
| 221: | |
| 222: | if (!ys) { |
| 223: | ys = line.match(/^(\s*)---(.*)$/) |
| 224: | if (ys) { |
| 225: | yind = ys[1] |
| 226: | diag = [ys[2]] |
| 227: | //console.error([line,ys, diag]) |
| 228: | continue |
| 229: | } else if (lt) this.emit("data", lt) |
| 230: | } else if (ys && !ye) { |
| 231: | if (line === yind + "...") ye = true |
| 232: | else { |
| 233: | diag.push(line.substr(yind.length)) |
| 234: | } |
| 235: | } else if (ys && ye && lt) this.emit("data", lt) |
| 236: | } |
| 237: | if (diag) { |
| 238: | //console.error('about to parse', diag) |
| 239: | diag = yamlish.decode(diag.join("\n")) |
| 240: | //console.error('parsed', diag) |
| 241: | Object.keys(diag).forEach(function (k) { |
| 242: | //console.error(this._current, k) |
| 243: | if (!this._current.hasOwnProperty(k)) this._current[k] = diag[k] |
| 244: | }, this) |
| 245: | } |
| 246: | } |
