Name: js-handler/node_modules/nodeunit/node_modules/tap/lib/tap-runner.js 
1:
var fs = require("fs")
2:
  , child_process = require("child_process")
3:
  , path = require("path")
4:
  , chain = require("slide").chain
5:
  , asyncMap = require("slide").asyncMap
6:
  , TapProducer = require("./tap-producer.js")
7:
  , TapConsumer = require("./tap-consumer.js")
8:
  , assert = require("./tap-assert.js")
9:
  , inherits = require("inherits")
10:
  , util = require("util")
11:
  , CovHtml = require("./tap-cov-html.js")
12:
  , glob = require("glob")
13:
 
14:
  // XXX Clean up the coverage options
15:
  , doCoverage = process.env.TAP_COV
16:
               || process.env.npm_package_config_coverage
17:
               || process.env.npm_config_coverage
18:
 
19:
module.exports = Runner
20:
 
21:
inherits(Runner, TapProducer)
22:
 
23:
function Runner (options, cb) {
24:
  this.options = options
25:
 
26:
  var diag = this.options.diag
27:
  var dir = this.options.argv.remain
28:
  TapProducer.call(this, diag)
29:
 
30:
  this.doCoverage = doCoverage
31:
  // An array of full paths to files to obtain coverage
32:
  this.coverageFiles = []
33:
  // The source of these files
34:
  this.coverageFilesSource = {}
35:
  // Where to write coverage information
36:
  this.coverageOutDir = this.options["coverage-dir"]
37:
  // Temporary test files bunkerified we'll remove later
38:
  this.f2delete = []
39:
  // Raw coverage stats, as read from JSON files
40:
  this.rawCovStats = []
41:
  // Processed coverage information, per file to cover:
42:
  this.covStats = {}
43:
 
44:
  if (dir) {
45:
    var filesToCover = this.options.cover
46:
 
47:
    if (doCoverage) {
48:
      var mkdirp = require("mkdirp")
49:
      this.coverageOutDir = path.resolve(this.coverageOutDir)
50:
      this.getFilesToCover(filesToCover)
51:
      var self = this
52:
      return mkdirp(this.coverageOutDir, 0755, function (er) {
53:
        if (er) return self.emit("error", er)
54:
        self.run(dir, cb)
55:
      })
56:
    }
57:
 
58:
    this.run(dir, cb)
59:
  }
60:
}
61:
 
62:
 
63:
Runner.prototype.run = function() {
64:
  var self = this
65:
    , args = Array.prototype.slice.call(arguments)
66:
    , cb = args.pop() || finish
67:
 
68:
  function finish (er) {
69:
    if (er) {
70:
      self.emit("error", er)
71:
    }
72:
 
73:
    if (!doCoverage) return self.end()
74:
 
75:
    // Cleanup temporary test files with coverage:
76:
    self.f2delete.forEach(function(f) {
77:
      fs.unlinkSync(f)
78:
    })
79:
    self.getFilesToCoverSource(function(err, data) {
80:
      if (err) {
81:
        self.emit("error", err)
82:
      }
83:
      self.getPerFileCovInfo(function(err, data) {
84:
        if (err) {
85:
          self.emit("error", err)
86:
        }
87:
        self.mergeCovStats(function(err, data) {
88:
          if (err) {
89:
            self.emit("error", err)
90:
          }
91:
          CovHtml(self.covStats, self.coverageOutDir, function() {
92:
            self.end()
93:
          })
94:
        })
95:
      })
96:
    })
97:
  }
98:
 
99:
  if (Array.isArray(args[0])) {
100:
    args = args[0]
101:
  }
102:
  self.runFiles(args, "", cb)
103:
}
104:
 
105:
Runner.prototype.runDir = function (dir, cb) {
106:
  var self = this
107:
  fs.readdir(dir, function (er, files) {
108:
    if (er) {
109:
      self.write(assert.fail("failed to readdir " + dir, { error: er }))
110:
      self.end()
111:
      return
112:
    }
113:
    files = files.sort(function(a, b) {
114:
      return a > b ? 1 : -1
115:
    })
116:
    files = files.filter(function(f) {
117:
      return !f.match(/^\./)
118:
    })
119:
    files = files.map(function(file) {
120:
      return path.resolve(dir, file)
121:
    })
122:
 
123:
    self.runFiles(files, path.resolve(dir), cb)
124:
  })
125:
}
126:
 
127:
 
128:
// glob the filenames so that test/*.js works on windows
129:
Runner.prototype.runFiles = function (files, dir, cb) {
130:
  var self = this
131:
  var globRes = []
132:
  chain(files.map(function (f) {
133:
    return function (cb) {
134:
      glob(f, function (er, files) {
135:
        if (er)
136:
          return cb(er)
137:
        globRes.push.apply(globRes, files)
138:
        cb()
139:
      })
140:
    }
141:
  }), function (er) {
142:
    if (er)
143:
      return cb(er)
144:
    runFiles(self, globRes, dir, cb)
145:
  })
146:
}
147:
 
148:
function runFiles(self, files, dir, cb) {
149:
  chain(files.map(function(f) {
150:
    return function (cb) {
151:
      if (self._bailedOut) return
152:
      var relDir = dir || path.dirname(f)
153:
        , fileName = relDir === "." ? f : f.substr(relDir.length + 1)
154:
 
155:
      self.write(fileName)
156:
      fs.lstat(f, function(er, st) {
157:
        if (er) {
158:
          self.write(assert.fail("failed to stat " + f, {error: er}))
159:
          return cb()
160:
        }
161:
 
162:
        var cmd = f, args = [], env = {}
163:
 
164:
        if (path.extname(f) === ".js") {
165:
          cmd = "node"
166:
          if (self.options.gc) {
167:
            args.push("--expose-gc")
168:
          }
169:
          args.push(fileName)
170:
        } else if (path.extname(f) === ".coffee") {
171:
          cmd = "coffee"
172:
          args.push(fileName)
173:
        } else {
174:
          // Check if file is executable
175:
          if ((st.mode & 0100) && process.getuid) {
176:
            if (process.getuid() != st.uid) {
177:
              return cb()
178:
            }
179:
          } else if ((st.mode & 0010) && process.getgid) {
180:
            if (process.getgid() != st.gid) {
181:
              return cb()
182:
            }
183:
          } else if ((st.mode & 0001) == 0) {
184:
            return cb()
185:
          }
186:
        }
187:
 
188:
        if (st.isDirectory()) {
189:
          return self.runDir(f, cb)
190:
        }
191:
 
192:
        if (doCoverage && path.extname(f) === ".js") {
193:
          var foriginal = fs.readFileSync(f, "utf8")
194:
            , fcontents = self.coverHeader() + foriginal + self.coverFooter()
195:
            , tmpBaseName = path.basename(f, path.extname(f))
196:
                          + ".with-coverage." + process.pid + path.extname(f)
197:
            , tmpFname = path.resolve(path.dirname(f), tmpBaseName)
198:
 
199:
          fs.writeFileSync(tmpFname, fcontents, "utf8")
200:
          args.splice(-1, 1, tmpFname)
201:
        }
202:
 
203:
        for (var i in process.env) {
204:
          env[i] = process.env[i]
205:
        }
206:
        env.TAP = 1
207:
 
208:
        var cp = child_process.spawn(cmd, args, { env: env, cwd: relDir })
209:
          , out = ""
210:
          , err = ""
211:
          , tc = new TapConsumer()
212:
          , childTests = [f]
213:
 
214:
        var timeout = setTimeout(function () {
215:
          if (!cp._ended) {
216:
            cp._timedOut = true
217:
            cp.kill()
218:
          }
219:
        }, self.options.timeout * 1000)
220:
 
221:
        tc.on("data", function(c) {
222:
          self.emit("result", c)
223:
          self.write(c)
224:
        })
225:
 
226:
        tc.on("bailout", function (message) {
227:
          clearTimeout(timeout)
228:
          console.log("# " + f.substr(process.cwd().length + 1))
229:
          process.stderr.write(err)
230:
          process.stdout.write(out + "\n")
231:
          self._bailedOut = true
232:
          cp._ended = true
233:
          cp.kill()
234:
        })
235:
 
236:
        cp.stdout.pipe(tc)
237:
        cp.stdout.on("data", function (c) { out += c })
238:
        cp.stderr.on("data", function (c) {
239:
          if (self.options.stderr) process.stderr.write(c)
240:
          err += c
241:
        })
242:
 
243:
        cp.on("close", function (code, signal) {
244:
          if (cp._ended) return
245:
          cp._ended = true
246:
          var ok = !cp._timedOut && code === 0
247:
          clearTimeout(timeout)
248:
          //childTests.forEach(function (c) { self.write(c) })
249:
          var res = { name: path.dirname(f).replace(process.cwd() + "/", "")
250:
                          + "/" + fileName
251:
                    , ok: ok
252:
                    , exit: code }
253:
 
254:
          if (cp._timedOut)
255:
            res.timedOut = cp._timedOut
256:
          if (signal)
257:
            res.signal = signal
258:
 
259:
          if (err) {
260:
            res.stderr = err
261:
            if (tc.results.ok &&
262:
                tc.results.tests === 0 &&
263:
                !self.options.stderr) {
264:
              // perhaps a compilation error or something else failed.
265:
              // no need if stderr is set, since it will have been
266:
              // output already anyway.
267:
              console.error(err)
268:
            }
269:
          }
270:
 
271:
          // tc.results.ok = tc.results.ok && ok
272:
          tc.results.add(res)
273:
          res.command = [cmd].concat(args).map(JSON.stringify).join(" ")
274:
          self.emit("result", res)
275:
          self.emit("file", f, res, tc.results)
276:
          self.write(res)
277:
          self.write("\n")
278:
          if (doCoverage) {
279:
            self.f2delete.push(tmpFname)
280:
          }
281:
          cb()
282:
        })
283:
      })
284:
    }
285:
  }), cb)
286:
 
287:
  return self
288:
}
289:
 
290:
 
291:
// Get an array of full paths to files we are interested into obtain
292:
// code coverage.
293:
Runner.prototype.getFilesToCover = function(filesToCover) {
294:
  var self = this
295:
  filesToCover = filesToCover.split(",").map(function(f) {
296:
    return path.resolve(f)
297:
  }).filter(function(f) {
298:
    return path.existsSync(f)
299:
  })
300:
 
301:
  function recursive(f) {
302:
    if (path.extname(f) === "") {
303:
      // Is a directory:
304:
      fs.readdirSync(f).forEach(function(p) {
305:
        recursive(f + "/" + p)
306:
      })
307:
    } else {
308:
      self.coverageFiles.push(f)
309:
    }
310:
  }
311:
  filesToCover.forEach(function(f) {
312:
    recursive(f)
313:
  })
314:
}
315:
 
316:
// Prepend to every test file to run. Note tap.test at the very top due it
317:
// "plays" with include paths.
318:
Runner.prototype.coverHeader = function() {
319:
  // semi here since we're injecting it before the first line,
320:
  // and don't want to mess up line numbers in the test files.
321:
  return "var ___TAP_COVERAGE = require("
322:
       + JSON.stringify(require.resolve("runforcover"))
323:
       + ").cover(/.*/g);"
324:
}
325:
 
326:
// Append at the end of every test file to run. Actually, the stuff which gets
327:
// the coverage information.
328:
// Maybe it would be better to move into a separate file template so editing
329:
// could be easier.
330:
Runner.prototype.coverFooter = function() {
331:
  var self = this
332:
  // This needs to be a string with proper interpolations:
333:
  return [ ""
334:
  , "var ___TAP = require(" + JSON.stringify(require.resolve("./main.js")) + ")"
335:
  , "if (typeof ___TAP._plan === 'number') ___TAP._plan ++"
336:
  , "___TAP.test(" + JSON.stringify("___coverage") + ", function(t) {"
337:
  , "  var covFiles = " + JSON.stringify(self.coverageFiles)
338:
  , "    , covDir = " + JSON.stringify(self.coverageOutDir)
339:
  , "    , path = require('path')"
340:
  , "    , fs = require('fs')"
341:
  , "    , testFnBase = path.basename(__filename, '.js') + '.json'"
342:
  , "    , testFn = path.resolve(covDir, testFnBase)"
343:
  , ""
344:
  , "  function asyncForEach(arr, fn, callback) {"
345:
  , "    if (!arr.length) {"
346:
  , "      return callback()"
347:
  , "    }"
348:
  , "    var completed = 0"
349:
  , "    arr.forEach(function(i) {"
350:
  , "      fn(i, function (err) {"
351:
  , "        if (err) {"
352:
  , "          callback(err)"
353:
  , "          callback = function () {}"
354:
  , "        } else {"
355:
  , "          completed += 1"
356:
  , "          if (completed === arr.length) {"
357:
  , "            callback()"
358:
  , "          }"
359:
  , "        }"
360:
  , "      })"
361:
  , "    })"
362:
  , "  }"
363:
  , ""
364:
  , "  ___TAP_COVERAGE(function(coverageData) {"
365:
  , "    var outObj = {}"
366:
  , "    asyncForEach(covFiles, function(f, cb) {"
367:
  , "      if (coverageData[f]) {"
368:
  , "        var stats = coverageData[f].stats()"
369:
  , "          , stObj = stats"
370:
  , "        stObj.lines = stats.lines.map(function (l) {"
371:
  , "          return { number: l.lineno, source: l.source() }"
372:
  , "        })"
373:
  , "        outObj[f] = stObj"
374:
  , "      }"
375:
  , "      cb()"
376:
  , "    }, function(err) {"
377:
  , "      ___TAP_COVERAGE.release()"
378:
  , "      fs.writeFileSync(testFn, JSON.stringify(outObj))"
379:
  , "      t.end()"
380:
  , "    })"
381:
  , "  })"
382:
  , "})" ].join("\n")
383:
}
384:
 
385:
 
386:
Runner.prototype.getFilesToCoverSource = function(cb) {
387:
  var self = this
388:
  asyncMap(self.coverageFiles, function(f, cb) {
389:
    fs.readFile(f, "utf8", function(err, data) {
390:
      var lc = 0
391:
      if (err) {
392:
        cb(err)
393:
      }
394:
      self.coverageFilesSource[f] = data.split("\n").map(function(l) {
395:
        lc += 1
396:
        return { number: lc, source: l }
397:
      })
398:
      cb()
399:
    })
400:
  }, cb)
401:
}
402:
 
403:
Runner.prototype.getPerFileCovInfo = function(cb) {
404:
  var self = this
405:
    , covPath = path.resolve(self.coverageOutDir)
406:
 
407:
  fs.readdir(covPath, function(err, files) {
408:
    if (err) {
409:
      self.emit("error", err)
410:
    }
411:
    var covFiles = files.filter(function(f) {
412:
      return path.extname(f) === ".json"
413:
    })
414:
    asyncMap(covFiles, function(f, cb) {
415:
      fs.readFile(path.resolve(covPath, f), "utf8", function(err, data) {
416:
        if (err) {
417:
          cb(err)
418:
        }
419:
        self.rawCovStats.push(JSON.parse(data))
420:
        cb()
421:
      })
422:
    }, function(f, cb) {
423:
      fs.unlink(path.resolve(covPath, f), cb)
424:
    }, cb)
425:
  })
426:
}
427:
 
428:
Runner.prototype.mergeCovStats = function(cb) {
429:
  var self = this
430:
  self.rawCovStats.forEach(function(st) {
431:
    Object.keys(st).forEach(function(i) {
432:
      // If this is the first time we reach this file, just add the info:
433:
      if (!self.covStats[i]) {
434:
        self.covStats[i] = {
435:
          missing: st[i].lines
436:
        }
437:
      } else {
438:
        // If we already added info for this file before, we need to remove
439:
        // from self.covStats any line not duplicated again (since it has
440:
        // run on such case)
441:
        self.covStats[i].missing = self.covStats[i].missing.filter(
442:
          function(l) {
443:
            return (st[i].lines.indexOf(l))
444:
          })
445:
      }
446:
    })
447:
  })
448:
 
449:
  // This is due to a bug into
450:
  // chrisdickinson/node-bunker/blob/feature/add-coverage-interface
451:
  // which is using array indexes for line numbers instead of the right number
452:
  Object.keys(self.covStats).forEach(function(f) {
453:
    self.covStats[f].missing = self.covStats[f].missing.map(function(line) {
454:
      return { number: line.number, source: line.source }
455:
    })
456:
  })
457:
 
458:
  Object.keys(self.coverageFilesSource).forEach(function(f) {
459:
    if (!self.covStats[f]) {
460:
      self.covStats[f] = { missing: self.coverageFilesSource[f]
461:
                          , percentage: 0
462:
      }
463:
    }
464:
    self.covStats[f].lines = self.coverageFilesSource[f]
465:
    self.covStats[f].loc = self.coverageFilesSource[f].length
466:
 
467:
    if (!self.covStats[f].percentage) {
468:
      self.covStats[f].percentage =
469:
        1 - (self.covStats[f].missing.length / self.covStats[f].loc)
470:
    }
471:
 
472:
  })
473:
  cb()
474:
}