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