[Pkg-javascript-commits] [node-jscoverage] 01/02: Imported Upstream version 0.5.0~rc2

Leo Iannacone l3on-guest at moszumanska.debian.org
Sun May 11 16:07:06 UTC 2014


This is an automated email from the git hooks/post-receive script.

l3on-guest pushed a commit to branch master
in repository node-jscoverage.

commit afe2ee0dcba5086498e0d5dd8ea7ba7292d3b1a8
Author: Leo Iannacone <l3on at ubuntu.com>
Date:   Sun May 11 17:42:26 2014 +0200

    Imported Upstream version 0.5.0~rc2
---
 .covignore                      |   3 +
 .jshintrc                       |  44 ++++++
 .npmignore                      |   1 +
 LICENSE                         |  24 +++
 README.md                       |  90 +++++++++++
 bin/jscoverage                  |  85 +++++++++++
 index.js                        | 262 +++++++++++++++++++++++++++++++
 lib/instrument.js               | 205 +++++++++++++++++++++++++
 lib/jscoverage.js               | 130 ++++++++++++++++
 lib/patch.js                    | 227 +++++++++++++++++++++++++++
 logo.png                        | Bin 0 -> 34900 bytes
 package.json                    |  43 ++++++
 reporter/detail.js              | 140 +++++++++++++++++
 reporter/html.js                | 148 ++++++++++++++++++
 reporter/summary.js             |  47 ++++++
 reporter/templates/coverage.ejs |  93 +++++++++++
 reporter/templates/script.ejs   |  34 +++++
 reporter/templates/style.ejs    | 330 ++++++++++++++++++++++++++++++++++++++++
 test/abc.js                     |  51 +++++++
 test/cde.js                     |  15 ++
 test/dir/a/a2                   |   3 +
 test/dir/a/test.md              |   1 +
 test/dir/a1.js                  |   3 +
 test/example.js                 | 120 +++++++++++++++
 test/index.js                   | 165 ++++++++++++++++++++
 test/jscoverage.js              |  43 ++++++
 test/patch.js                   |  30 ++++
 test/reporter_detail.js         |  38 +++++
 28 files changed, 2375 insertions(+)

diff --git a/.covignore b/.covignore
new file mode 100644
index 0000000..62a0581
--- /dev/null
+++ b/.covignore
@@ -0,0 +1,3 @@
+/test/index.js
+/test/jscoverage.js
+/test/test.js
\ No newline at end of file
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 0000000..6c3451b
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,44 @@
+{
+  "predef": [
+    "document",
+    "module",
+    "require",
+    "__dirname",
+    "process",
+    "console",
+    "it",
+    "xit",
+    "describe",
+    "xdescribe",
+    "before",
+    "beforeEach",
+    "after",
+    "afterEach"
+  ],
+  "node": true,
+  "es5": true,
+  "bitwise": true,
+  "curly": true,
+  "eqeqeq": true,
+  "forin": false,
+  "immed": true,
+  "latedef": true,
+  "newcap": true,
+  "noarg": true,
+  "noempty": true,
+  "nonew": true,
+  "plusplus": false,
+  "undef": true,
+  "strict": false,
+  "trailing": false,
+  "globalstrict": true,
+  "nonstandard": true,
+  "white": false,
+  "indent": 2,
+  "expr": true,
+  "multistr": true,
+  "onevar": false,
+  "unused": "vars",
+  "sub": true,
+  "quoteMark": true
+}
\ No newline at end of file
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..3c3629e
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1 @@
+node_modules
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..c6d4fc5
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,24 @@
+(The MIT License)
+
+Copyright (c) 2011-2013 fish <zhengxinlin at gmail.com>
+
+Based on Uglify-js https://github.com/mishoo/UglifyJS
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..9be7f78
--- /dev/null
+++ b/README.md
@@ -0,0 +1,90 @@
+jscoverage
+==========
+![logo](https://raw.github.com/fishbar/jscoverage/master/logo.png)
+
+jscoverage tool, both node.js or javascript support
+
+[![Build Status](https://travis-ci.org/fishbar/jscoverage.svg)](https://travis-ci.org/fishbar/jscoverage)
+
+
+### install
+
+```sh
+npm install jscoverage
+```
+
+### changelog
+
+from v0.5.0, jscoverage start using uglify2, and enhance the coverage range.
+now, jscoverage will find out which branch you missed!
+jscoverage can run with mocha, even with mocha's html-cov reporter.
+since mocha's html-cov dose not display branch cov info, jscoverage supply a new html-cov reporter
+
+### get source code
+
+```sh
+git clone git://github.com/fishbar/jscoverage.git
+```
+
+### using jscoverage with mocha
+
+let mocha load jscoverage using -r options, like:
+```sh
+mocha -r jscoverage --covignore .covignore --covout html --covinject true test
+```
+the case above, mocha do nothing with these options: --covignore , --covout --covinject
+but jscoverage can recognise them, all support options are here:
+
+  --covignore [filepath] # like gitignore, tell jscoverage to ignore these files
+
+  --covout [output report] # can be: summary, detail, json, html, lcov, default summary
+
+  --coverage [high,middle,low] # coverage level, default is: 90,70,30 , means 90% is high, 30% is low
+
+  --covinject [boolean] # switch if inject code for easytest(exports._get, _replace, _reset), default is false
+
+default jscoverage will search .covignore in the project root
+
+### using jscoverage as cli command
+
+```shell
+jscoverage
+# print help info
+jscoverage source.js
+# convert source.js to source-cov.js
+jscoverage source.js dest.js
+# convert source.js to dest.js
+jscoverage sourcedir destdir --exclude a.js,b.js,c.js,*.min.js
+# convert all files in sourcedir to destdir, exclude list will be ignored
+```
+jscoverage will copy exclude file from source dir to dest dir
+
+### using jscoverage programmatically
+
+comming soon
+
+### using inject api for node.js test
+
+```js
+var testMod = require('module/for/test.js');
+
+testMod._get('name');
+testMod._replace('name', value);
+testMod._reset();
+testMod._call();
+```
+### inline ignore annotation
+
+using bellow comment, jscoverage will ignore the following block/statement
+
+```js
+  /* @covignore */
+```
+
+### mocha global leaks detect
+
+The follow object will be detected, all of them are created by jscoverage.
+
+  * _$jscoverage
+  * _$jscmd
+
diff --git a/bin/jscoverage b/bin/jscoverage
new file mode 100755
index 0000000..25fa00c
--- /dev/null
+++ b/bin/jscoverage
@@ -0,0 +1,85 @@
+#!/usr/bin/env node
+/**
+ * [argv description]
+ *  * @usage
+ *   # cli command
+ *
+ *   # using as a node module
+ */
+var argv = require('optimist').argv;
+var jscoverage = require('../index');
+var paths =  argv._;
+var source = paths[0];
+var dest = paths[1];
+var exclude = argv.exclude;
+var fs = require('xfs');
+var path = require('path');
+
+if (!source) {
+  helpInfo();
+  process.exit();
+}
+var sourceStat;
+
+if (!dest) {
+  if (/\.\w+$/.test(source)) {
+    dest = source.replace(/(\.\w+)$/, '-cov$1');
+  } else {
+    dest = source.replace(/(\/|\\)$/, '') + '-cov';
+  }
+}
+
+if (exclude) {
+  exclude = exclude.split(',');
+  exclude.forEach(function (v, i, a) {
+    a[i] = new RegExp(v.replace(/\./, '\\.').replace(/\*/g, '.*'));
+  });
+}
+try {
+  sourceStat = fs.statSync(source);
+  if (sourceStat.isFile()) {
+    jscoverage.processFile(source, dest);
+  } else {
+    var count = 0;
+    fs.walk(source, function (err, file, done) {
+      if (err) {
+        console.error(err);
+        return done;
+      }
+      var flag = false;
+      if (exclude) {
+        exclude.forEach(function (v) {
+          if (v.test(file)) {
+            flag = true;
+          }
+        });
+      }
+      count ++;
+      var destFile = path.join(dest, file.substr(source.length));
+      if (flag) {
+        // copy exclude file
+        fs.save(destFile, fs.readFileSync(file));
+      } else {
+        jscoverage.processFile(file, destFile);
+      }
+      done();
+    }, function (err) {
+      if (err) {
+        return console.error(err);
+      }
+      console.log('process files:', count);
+    });
+  }
+} catch (e) {
+  console.log('source file not exist!', e);
+  process.exit();
+}
+
+function helpInfo() {
+  var help = [];
+  help.push('usage:');
+  help.push('\t> jscoverage source');
+  help.push('\t## this is equal : jscoverage source source-cov | jscoverage source.js source-cov.js');
+  help.push('\t> jscoverage source dest --exclude a,b,c,d');
+  console.log(help.join('\n'));
+}
diff --git a/index.js b/index.js
new file mode 100755
index 0000000..5ec4668
--- /dev/null
+++ b/index.js
@@ -0,0 +1,262 @@
+/*!
+ * jscoverage: index.js
+ * Authors  : fish <zhengxinlin at gmail.com> (https://github.com/fishbar)
+ * Create   : 2014-04-03 15:20:13
+ * CopyRight 2014 (c) Fish And Other Contributors
+ *
+ */
+var fs = require('xfs');
+var path = require('path');
+var argv = require('optimist').argv;
+var patch = require('./lib/patch');
+var cmd = argv['$0'];
+var MODE_MOCHA = false;
+var FLAG_LOCK = false;
+if (/mocha/.test(cmd)) {
+  MODE_MOCHA = true;
+}
+
+if (MODE_MOCHA) {
+  prepareMocha();
+}
+/**
+ * prepare env for mocha test
+ * @covignore
+ */
+function prepareMocha() {
+  var covIgnore = argv.covignore;
+  var cwd = process.cwd();
+  var covlevel = argv.coverage;
+  if (covlevel) {
+    var tmp = covlevel.split(',');
+    covlevel = {
+      high: parseFloat(tmp[0], 10),
+      middle: parseFloat(tmp[1], 10),
+      low: parseFloat(tmp[2], 10)
+    };
+  } else {
+    covlevel = {
+      high: 0.9,
+      middle: 0.7,
+      low: 0.3
+    };
+  }
+  /**
+   * add after hook
+   * @return {[type]} [description]
+   */
+  process.nextTick(function () {
+    try {
+      after(function () {
+        if (FLAG_LOCK) {
+          return;
+        }
+        FLAG_LOCK = true;
+        if (typeof _$jscoverage === 'undefined') {
+          return;
+        } 
+        try {
+          if (argv.covout === 'none') {
+            return;
+          }
+          if (!argv.covout) {
+            argv.covout = 'summary';
+          }
+          var reporter;
+          if (/^\w+$/.test(argv.covout)) {
+            reporter = require('./reporter/' + argv.covout);
+          } else {
+            reporter = require(argv.covout);
+          }
+          reporter.process(_$jscoverage, exports.coverageStats(), covlevel);
+        } catch (e) {
+          console.error('jscoverage reporter error', e, e.stack);
+        }
+      });
+    } catch (e) {
+      // do nothing
+    }
+  });
+  if (argv.covinject) {
+    patch.enableInject(true);
+  }
+  if (!covIgnore) {
+    try {
+      var stat = fs.statSync('.covignore');
+      stat && (covIgnore = '.covignore');
+    } catch (e) {
+      return;
+    }
+  }
+  try {
+    covIgnore = fs.readFileSync(covIgnore).toString().split(/\r?\n/g);
+  } catch (e) {
+    throw new Error('jscoverage loading covIgnore file error:' + covIgnore);
+  }
+  covIgnore.forEach(function (v, i, a) {
+    if (v.indexOf('/') === 0) {
+      v = '^' + cwd + v;
+    }
+    a[i] = new RegExp(v.replace(/\./g, '\\.').replace(/\*/g, '.*'));
+  });
+
+  patch.setCovIgnore(covIgnore);
+}
+
+var jscoverage = require('./lib/jscoverage');
+
+/**
+ * enableInject description
+ * @param {Boolean} true or false
+ */
+exports.enableInject = patch.enableInject;
+/**
+ * config the inject function names
+ * @param  {Object} obj  {get, replace, call, reset}
+ * @example
+ *
+ *  jsc.config({get:'$get', replace:'$replace'});
+ *
+ *  =====================
+ *
+ *  testMod = require('testmodule');
+ *  testMod.$get('name');
+ *  testMod.$replace('name', obj);
+ */
+exports.config = function (obj) {
+  var inject_functions = patch.getInjectFunctions();
+  for (var i in obj) {
+    inject_functions[i] = obj[i];
+  }
+};
+/**
+ * process Code, inject the coverage code to the input Code string
+ * @param {String} filename  jscoverage file flag
+ * @param {Code} content
+ * @return {Code} instrumented code
+ */
+exports.process = jscoverage.process;
+
+/**
+ * processFile, instrument singfile
+ * @sync
+ * @param  {Path} source  absolute Path
+ * @param  {Path} dest    absolute Path
+ * @param  {Object} option  [description]
+ */
+exports.processFile = function (source, dest, option) {
+  var content;
+  var stats;
+  // test source is file or dir, or not a file
+  try {
+    stats = fs.statSync(source);
+    if (stats.isDirectory()) {
+      throw new Error('path is dir');
+    } else if (!stats.isFile()) {
+      throw new Error('path is not a regular file');
+    }
+  } catch (e) {
+    throw new Error('source file error' + e);
+  }
+
+  fs.sync().mkdir(path.dirname(dest));
+
+  content = fs.readFileSync(source).toString();
+  content = content.toString();
+  content = this.process(source, content);
+  fs.writeFileSync(dest, content);
+};
+
+
+/**
+ * sum the coverage rate
+ * @public
+ */
+exports.coverageStats = function () {
+  var file;
+  var tmp;
+  var total;
+  var touched;
+  var n, len;
+  var stats = {};
+  var conds, condsMap, cond;
+  var line, start, offset;
+  if (typeof _$jscoverage === 'undefined') {
+    return;
+  }
+  for (var i in _$jscoverage) {
+    file = i;
+    tmp = _$jscoverage[i];
+    if (!tmp.length) {
+      continue;
+    }
+    total = touched = 0;
+    for (n = 0, len = tmp.length; n < len; n++) {
+      if (tmp[n] !== undefined) {
+        total ++;
+        if (tmp[n] > 0) {
+          touched ++;
+        }
+      }
+    }
+    conds = tmp.condition;
+    condsMap = {};
+    for (n in conds) {
+      if (conds[n] === 0) {
+        cond = n.split('_');
+        line = cond[0];
+        start = parseInt(cond[1], 10);
+        offset = parseInt(cond[2], 10);
+        if (!condsMap[line]) {
+          condsMap[line] = [];
+        }
+        condsMap[line].push([start, offset]);
+      } else {
+        touched ++;
+      }
+      total ++;
+    }
+    stats[file] = {
+      sloc: total,
+      hits: touched,
+      coverage: total ? touched / total : 0,
+      percent: total ? ((touched / total) * 100).toFixed(2) + '%' : '~',
+      condition: condsMap
+    };
+  }
+  return stats;
+};
+
+/**
+ * get lcov report
+ * @return {[type]} [description]
+ */
+exports.getLCOV = function () {
+  var tmp;
+  var total;
+  var touched;
+  var n, len;
+  var lcov = '';
+  if (typeof _$jscoverage === 'undefined') {
+    return;
+  }
+  Object.keys(_$jscoverage).forEach(function (file) {
+    lcov += 'SF:' + file + '\n';
+    tmp = _$jscoverage[file];
+    if (!tmp.length) {
+      return;
+    }
+    total = touched = 0;
+    for (n = 0, len = tmp.length; n < len; n++) {
+      if (tmp[n] !== undefined) {
+        lcov += 'DA:' + n + ',' + tmp[n] + '\n';
+        total ++; 
+        if (tmp[n] > 0) {
+          touched++;
+        } 
+      }
+    }
+    lcov += 'end_of_record\n';
+  });
+  return lcov;
+};
diff --git a/lib/instrument.js b/lib/instrument.js
new file mode 100755
index 0000000..7cb2e78
--- /dev/null
+++ b/lib/instrument.js
@@ -0,0 +1,205 @@
+/*!
+ * jscoverage: lib/instrument.js
+ * Authors  : fish <zhengxinlin at gmail.com> (https://github.com/fishbar)
+ * Create   : 2014-04-03 15:20:13
+ * CopyRight 2014 (c) Fish And Other Contributors
+ */
+/**
+ * instrument code
+ * @example
+ *   var ist = new Instrument();
+ *   var resCode = ist.process(str);
+ */
+var Uglify = require('uglify-js');
+
+function Instrument() {
+  /**
+   * filename needed
+   * @type {String}
+   */
+  this.filename = null;
+  /**
+   * store injected code
+   * @type {String}
+   */
+  this.code = null;
+  /**
+   * 储存line信息
+   * @type {Array}
+   */
+  this.lines = [];
+  /**
+   * 储存condition信息
+   * @type {Object}
+   */
+  this.conds = {};
+  /**
+   * source code in array
+   * @type {Array}
+   */
+  this.source = null;
+}
+
+Instrument.prototype = {
+  // 行类型
+  T_LINE: 'line',
+  T_COND: 'cond',
+  /**
+   * process code
+   * @public
+   * @param  {String} code source code
+   * @return {String} injected code
+   */
+  process: function (filename, code) {
+    if (!filename) {
+      throw new Error('[jscoverage]instrument need filename!');
+    }
+
+    var ist = this;
+    // parse ast
+    var ast = Uglify.parse(code);
+
+    this.filename = filename;
+    this.source = code.split(/\r?\n/);
+
+    // init walker
+    var walker = new Uglify.TreeWalker(function (node) {
+      if (ist.checkIfIgnore(node, walker.stack)) {
+        return;
+      }
+
+      if (node instanceof Uglify.AST_Conditional) { // 三元判断
+        node.consequent = ist.inject('cond', node.consequent.start.line, node.consequent);
+        node.alternative = ist.inject('cond', node.alternative.start.line, node.alternative);
+      } else if (node.TYPE === 'Binary') {
+        if (!(node.left instanceof Uglify.AST_Constant)) {
+          node.left = ist.inject('cond', node.left.start.line, node.left);
+        }
+        if (!(node.right instanceof Uglify.AST_Constant)) {
+          node.right = ist.inject('cond', node.right.start.line, node.right);
+        }
+      }
+      var len = node.body ? node.body.length : 0;
+      if (len) {
+        var res = [];
+        var subNode;
+        for (var i = 0; i < len; i++) {
+          subNode = node.body[i];
+          if (ist.checkIfIgnore(subNode, walker.stack)) {
+            res.push(subNode);
+            continue;
+          }
+          if (subNode instanceof Uglify.AST_Statement) {
+            if (ist.ifExclude(subNode)) {
+              res.push(subNode);
+              continue;
+            }
+            res.push(ist.inject('line', subNode.start.line));
+          } else if (subNode instanceof Uglify.AST_Var) {
+            res.push(ist.inject('line', subNode.start.line));
+          }
+          res.push(subNode);
+        }
+        node.body = res;
+      }
+    });
+    // figure_out_scope
+    ast.figure_out_scope();
+    // walk process
+    ast.walk(walker);
+
+    var out = Uglify.OutputStream({
+      preserve_line : true,
+      comments: 'all',
+      beautify: true
+    });
+    // rebuild file
+    ast.print(out);
+    this.code = out.toString();
+    return this;
+  },
+  /**
+   * 注入覆盖率查询方法
+   * @private
+   * @param  {String} type  inject type, line | conds
+   * @param  {Number} line  line number
+   * @param  {Object} expr  any expression, or node, or statement
+   * @return {AST_Func} Object
+   */
+  inject: function (type, line, expr) {
+    var args = [];
+    if (type === this.T_LINE) {
+      this.lines.push(line);
+      args = [
+        new Uglify.AST_String({value: this.filename}),
+        new Uglify.AST_String({value: type}),
+        new Uglify.AST_Number({value: line})
+      ];
+    } else if (type === this.T_COND) {
+      var start = expr.start.col;
+      var offset = expr.end.endpos - expr.start.pos;
+      var key = line + '_' + start + '_' + offset;  // 编码
+      this.conds[key] = 0;
+      args = [
+        new Uglify.AST_String({value: this.filename}),
+        new Uglify.AST_String({value: type}),
+        new Uglify.AST_String({value: key}),
+        expr
+      ];
+    }
+  
+    var call = new Uglify.AST_Call({
+      expression: new Uglify.AST_SymbolRef({name: '_$jscmd'}),
+      //end: new Uglify.AST_
+      args: args
+    });
+
+    if (type === this.T_LINE) {
+      return new Uglify.AST_SimpleStatement({
+        body: call,
+        end: new Uglify.AST_Token({value: ';'})
+      });
+    } else {
+      return call;
+    }
+  },
+  /**
+   * check if need inject
+   * @param  {AST_Node} node
+   * @return {Boolean} 
+   */
+  ifExclude: function (node) {
+    if (node instanceof Uglify.AST_LoopControl) {
+      return false;
+    }
+    if (
+      node instanceof Uglify.AST_IterationStatement ||
+      node instanceof Uglify.AST_StatementWithBody ||
+      node instanceof Uglify.AST_Block
+    ) {
+      return true;
+    }
+  },
+  checkIfIgnore: function (node, stack) {
+    var cmt;
+    if (node.start && node.start.comments_before.length) {
+      cmt = node.start.comments_before[node.start.comments_before.length - 1];
+      if (/@covignore/.test(cmt.value) && !(node instanceof Uglify.AST_Toplevel)) {
+        node.__covignore = true;
+      }
+    }
+    if (node.__covignore) {
+      return true;
+    }
+    if (stack) {
+      for (var i = stack.length - 1; i > 0; i--) {
+        if (stack[i].__covignore) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+};
+
+module.exports = Instrument;
\ No newline at end of file
diff --git a/lib/jscoverage.js b/lib/jscoverage.js
new file mode 100755
index 0000000..422d1d2
--- /dev/null
+++ b/lib/jscoverage.js
@@ -0,0 +1,130 @@
+/*!
+ * jscoverage: lib/jscoverage.js
+ * Authors  : fish <zhengxinlin at gmail.com> (https://github.com/fishbar)
+ * Create   : 2014-04-03 15:20:13
+ * CopyRight 2014 (c) Fish And Other Contributors
+ */
+var Instrument = require('./instrument');
+
+/**
+ * do not exec this function
+ * the function body will insert into instrument files
+ *
+ * _$jscoverage = {
+ *   filename : {
+ *     line1: 0
+ *     line2: 1
+ *     line3: undefined
+ *     ....
+ *     source: [],
+ *     condition: [
+ *       line_start_offset
+ *     ]
+ *   }
+ * }
+ */
+function jscFunctionBody() {
+  // instrument by jscoverage, do not modifly this file
+  (function (file, lines, conds, source) {
+    var BASE;
+    if (typeof global === 'object') {
+      BASE = global;
+    } else if (typeof window === 'object') {
+      BASE = window;
+    } else {
+      throw new Error('[jscoverage] unknow ENV!');
+    }
+    if (BASE._$jscoverage) {
+      BASE._$jscmd(file, 'init', lines, conds, source);
+      return;
+    }
+    var cov = {};
+    /**
+     * jsc(file, 'init', lines, condtions)
+     * jsc(file, 'line', lineNum)
+     * jsc(file, 'cond', lineNum, expr, start, offset)
+     */
+    function jscmd(file, type, line, express, start, offset) {
+      var storage;
+      switch (type) {
+        case 'init':
+          storage = [];
+          for (var i = 0; i < line.length; i ++) {
+            storage[line[i]] = 0;
+          }
+          var condition = express;
+          var source = start;
+          storage.condition = condition;
+          storage.source = source;
+          cov[file] = storage;
+          break;
+        case 'line':
+          storage = cov[file];
+          storage[line] ++;
+          break;
+        case 'cond':
+          storage = cov[file];
+          storage.condition[line] ++;
+          return express;
+      }
+    }
+    
+    BASE._$jscoverage = cov;
+    BASE._$jscmd = jscmd;
+    jscmd(file, 'init', lines, conds, source);
+  })('$file$', $lines$, $conds$, $source$);
+}
+/**
+ * gen coverage head
+ */
+function genCodeCoverage(instrObj) {
+  if (!instrObj) {
+    return '';
+  }
+  var code = [];
+  var filename = instrObj.filename;
+  // Fix windows path
+  filename = filename.replace(/\\/g, '/');
+  var lines = instrObj.lines;
+  var conditions = instrObj.conds;
+  var src = instrObj.source;
+  var jscfArray = jscFunctionBody.toString().split('\n');
+  jscfArray = jscfArray.slice(1, jscfArray.length - 1);
+  var ff = jscfArray.join('\n').replace(/(^|\n) {2}/g, '\n')
+    .replace(/\$(\w+)\$/g, function (m0, m1){
+      switch (m1) {
+        case 'file':
+          return filename;
+        case 'lines':
+          return JSON.stringify(lines);
+        case 'conds':
+          return JSON.stringify(conditions);
+        case 'source':
+          return JSON.stringify(src);
+      }
+    });
+  code.push(ff);
+  code.push(instrObj.code);
+  return code.join('\n');
+}
+
+exports.process = function (filename, content) {
+  if (!filename) {
+    throw new Error('jscoverage.process(filename, content), filename needed!');
+  }
+  filename = filename.replace(/\\/g, '/');
+  if (!content) {
+    return '';
+  }
+  var pwd = process.cwd();
+  var fname;
+  if (filename.indexOf(pwd) === 0) {
+    fname = filename.substr(pwd.length + 1);
+  } else {
+    fname = filename;
+  }
+  var instrObj;
+  var ist = new Instrument();
+  instrObj = ist.process(fname, content);
+  return genCodeCoverage(instrObj);
+};
diff --git a/lib/patch.js b/lib/patch.js
new file mode 100644
index 0000000..e438bbd
--- /dev/null
+++ b/lib/patch.js
@@ -0,0 +1,227 @@
+/*!
+ * jscoverage: lib/patch.js
+ * Authors  : fish <zhengxinlin at gmail.com> (https://github.com/fishbar)
+ * Create   : 2014-04-03 15:20:13
+ * CopyRight 2014 (c) Fish And Other Contributors
+ */
+var Module = require('module');
+var path = require('path');
+var fs = require('fs');
+var argv = require('optimist').argv;
+var jscoverage = require('./jscoverage');
+
+var covInject = false;
+var defaultCovIgnore = [
+  new RegExp('^' + process.cwd() + '/node_modules/'),
+  new RegExp('^' + process.cwd() + '/test/')
+];
+var covIgnore = defaultCovIgnore;
+
+var injectFunctions = {
+  get : '_get',
+  replace : '_replace',
+  call : '_call',
+  reset : '_reset',
+  test: '_test'
+};
+
+exports.getInjectFunctions = function () {
+  return injectFunctions;
+};
+
+exports.enableInject = function (bool) {
+  covInject = bool;
+};
+exports.setCovIgnore = function (ignore) {
+  covIgnore = ignore.concat(defaultCovIgnore);
+};
+/**
+ * do mock things here
+ * @covignore
+ */
+(function () {
+  if (Module.prototype.__jsc_patch__) {
+    return;
+  }
+  Module.prototype.__jsc_patch__ = true;
+  var origin_require = Module.prototype.require;
+  Module.prototype.require = function (filename) {
+    var needinject = covInject;
+    var ff = filename;
+    filename = Module._resolveFilename(filename, this);
+    var flagjsc = checkModule(filename);
+    if (typeof filename  === 'object') {
+      filename = filename[0];
+    }
+
+    if (!flagjsc) {
+      return origin_require.call(this, filename);
+    }
+    
+    var cachedModule = Module._cache[filename];
+    // take care of module cache
+    if (flagjsc && cachedModule && cachedModule.__coveraged__) {
+      return cachedModule.exports;
+    }
+    // console.log('jscoverage:', ff, 'cov', flagjsc, 'inject', needinject);
+    var module = new Module(filename, this);
+    try {
+      module.filename = filename;
+      module.paths = Module._nodeModulePaths(path.dirname(filename));
+      Module._extensions['.js'](module, filename, {
+        flagjsc : flagjsc,
+        needinject : needinject
+      });
+      module.__coveraged__ = flagjsc;
+      module.loaded = true;
+      Module._cache[filename] = module;
+    } catch (err) {
+      delete Module._cache[filename];
+      console.error(err.stack);
+      throw err;
+    }
+    return module.exports;
+  };
+  function stripBOM(content) {
+    // Remove byte order marker. This catches EF BB BF (the UTF-8 BOM)
+    // because the buffer-to-string conversion in `fs.readFileSync()`
+    // translates it to FEFF, the UTF-16 BOM.
+    if (content.charCodeAt(0) === 0xFEFF) {
+      content = content.slice(1);
+    }
+    return content;
+  }
+  Module._extensions['.js'] = function (module, filename, status) {
+    var content = fs.readFileSync(filename, 'utf8');
+    var tmpFuncBody;
+    var injectFn = exports.getInjectFunctions();
+    // trim first line when script is a shell script
+    // content = content.replace(/^\#\![^\n]+\n/, '');
+    if (status && status.flagjsc) {
+      content = jscoverage.process(filename, content);
+    }
+    if (status && status.needinject) {
+      tmpFuncBody = injectFunctionBody.toString().replace(/\$\$(\w+)\$\$/g, function (m0, m1) {
+        return injectFunctions[m1];
+      });
+      tmpFuncBody = tmpFuncBody.split(/\n/);
+      content += '\n' + tmpFuncBody.slice(1, tmpFuncBody.length - 1).join('\n');
+    }
+    module._compile(stripBOM(content), filename);
+  };
+})();
+
+function checkModule(module) {
+
+  // native module
+  if (!/\//.test(module)) {
+    return false;
+  }
+  // modules in node_modules
+  var flagIgnore = false;
+  covIgnore.forEach(function (v) {
+    if (v.test(module)) {
+      flagIgnore = true;
+    }
+  });
+  return !flagIgnore;
+}
+
+/**
+ * do not exec this function
+ * @covignore
+ */
+function injectFunctionBody() {
+  (function (){
+  if (module.exports._i_n_j_e_c_t_e_d_) {
+    return;
+  }
+  if (module.exports.$$call$$ || module.exports.$$get$$ ||
+      module.exports.$$replace$$ || module.exports.$$reset$$) {
+    throw new Error("[jscoverage] jscoverage can not inject function for this module, because the function is exists! using jsc.config({inject:{}})");
+  }
+
+  var __r_e_p_l_a_c_e__ = {};
+  module.exports.$$replace$$ = function (name, obj) {
+    function stringify(obj) {
+      if (obj === null) {
+        return 'null';
+      }
+      if (obj === undefined){
+        return 'undefined';
+      }
+      if (!obj && isNaN(obj)){
+        return 'NaN';
+      }
+      if (typeof obj === 'string') {
+        return '"' + obj.replace(/"/g, '\\"') + '"';
+      }
+      if (typeof obj === 'number') {
+        return obj;
+      }
+      if (obj.constructor === Date) {
+        return 'new Date(' + obj.getTime() + ')';
+      }
+      if (obj.constructor === Function) {
+        return obj.toString();
+      }
+      if (obj.constructor === RegExp) {
+        return obj.toString();
+      }
+      var is_array = obj.constructor === Array ? true : false;
+      var res, i;
+      if (is_array) {
+        res = ['['];
+        for (i = 0; i < obj.length; i++) {
+          res.push(stringify(obj[i]));
+          res.push(',');
+        }
+        if (res[res.length - 1] === ',') {
+          res.pop();
+        }
+        res.push(']');
+      } else {
+        res = ['{'];
+        for (i in obj) {
+          res.push(i + ':' + stringify(obj[i]));
+          res.push(',');
+        }
+        if (res[res.length - 1] === ',')
+          res.pop();
+        res.push('}');
+      }
+      return res.join('');
+    }
+    if (!__r_e_p_l_a_c_e__.hasOwnProperty(name)) {
+        __r_e_p_l_a_c_e__[name] = eval(name);
+      }
+    eval(name + "=" + stringify(obj));
+  };
+  module.exports.$$reset$$ = function (name) {
+    var script;
+    if (name) {
+      script = 'if(__r_e_p_l_a_c_e__.hasOwnProperty("' + name + '"))' + name + ' = __r_e_p_l_a_c_e__["' + name + '"];';
+    } else {
+      script = 'for(var i in __r_e_p_l_a_c_e__){eval( i + " = __r_e_p_l_a_c_e__[\'" + i + "\'];");}';
+    }
+    eval(script);
+  };
+  module.exports.$$call$$ = module.exports.$$test$$ = function (func, args) {
+    var f, o;
+    if (func.match(/\\./)) {
+      func = func.split(".");
+      f = func[func.length - 1];
+      func.pop();
+      o = func.join(".");
+    } else {
+      f = func;
+      o = "this";
+    }
+    return eval(f + ".apply(" + o + "," + JSON.stringify(args) + ")");
+  };
+  module.exports.$$get$$ = function (objstr) {
+    return eval(objstr);
+  };
+  module.exports._i_n_j_e_c_t_e_d_ = true;
+})();
+}
diff --git a/logo.png b/logo.png
new file mode 100644
index 0000000..154ca97
Binary files /dev/null and b/logo.png differ
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..86c2f63
--- /dev/null
+++ b/package.json
@@ -0,0 +1,43 @@
+{
+  "name": "jscoverage",
+  "version": "0.5.0-rc2",
+  "description": "a javascript coverage tool, can be used in node dev, and browser side js dev",
+  "main": "index.js",
+  "bin": { 
+    "jscoverage": "./bin/jscoverage"
+  },
+  "scripts": {
+    "test": "./node_modules/mocha/bin/_mocha -r ./index.js --covinject true test/"
+  },
+  "engines": {
+    "node" : ">=0.8"
+  },
+  "dependencies" : {
+    "uglify-js" : "2.4.13",
+    "optimist" : "0.3.1",
+    "xfs" : "0.1.7",
+    "ejs": "1.0.0"
+  },
+  "devDependencies" : {
+    "mocha" : "*",
+    "expect.js" : "*",
+    "xfs" : "*"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/fishbar/jscoverage.git"
+  },
+  "keywords": [
+    "jscoverage",
+    "node",
+    "javascript",
+    "coverage",
+    "dev",
+    "tool"
+  ],
+  "author": "fish <zhengxinlin at gmail.com>, kate.sf <kate.sf at taobao.com>",
+  "contributors":[
+    { "name": "christineRR", "email": "rongkunli1215 at gmail.com" }
+  ],
+  "license": "MIT"
+}
diff --git a/reporter/detail.js b/reporter/detail.js
new file mode 100644
index 0000000..c1909e6
--- /dev/null
+++ b/reporter/detail.js
@@ -0,0 +1,140 @@
+/*!
+ * jscoverage: reporter/detail.js
+ * Authors  : fish <zhengxinlin at gmail.com> (https://github.com/fishbar)
+ * Create   : 2014-04-10 16:23:23
+ * CopyRight 2014 (c) Fish And Other Contributors
+ */
+
+/**
+ * print detail coverage info in console
+ * @param  {Object} _$jscoverage [description]
+ * @param  {Object} stats        [description]
+ * @param  {Number} covlevel       [description]
+ */
+exports.process = function (_$jscoverage, stats, covlevel) {
+  var file;
+  var tmp;
+  var source;
+  var lines;
+  var allcovered;
+  for (var i in _$jscoverage) {
+    file = i;
+    tmp = _$jscoverage[i];
+    if (typeof tmp === 'function' || tmp.length === undefined) {
+      continue;
+    }
+    source = tmp.source;
+    allcovered = true;
+    //console.log('[JSCOVERAGE]',file);
+    console.log('[UNCOVERED CODE]', file);
+    lines = [];
+    for (var n = 0, len = source.length; n < len ; n++) {
+      if (tmp[n] === 0) {
+        lines[n] = 1;
+        allcovered = false;
+      } else {
+        lines[n] = 0;
+      }
+    }
+    if (allcovered) {
+      console.log(colorful('\t100% covered', 'GREEN'));
+    } else {
+      printCoverageDetail(lines, source);
+    }
+  }
+};
+
+function processLinesMask(lines) {
+  function processLeft3(arr, offset) {
+    var prev1 = offset - 1;
+    var prev2 = offset - 2;
+    var prev3 = offset - 3;
+    if (prev1 < 0) {
+      return;
+    }
+    arr[prev1] = arr[prev1] === 1 ? arr[prev1] : 2;
+    if (prev2 < 0) {
+      return;
+    }
+    arr[prev2] = arr[prev2] === 1 ? arr[prev2] : 2;
+    if (prev3 < 0) {
+      return;
+    }
+    arr[prev3] = arr[prev3] ? arr[prev3] : 3;
+  }
+  function processRight3(arr, offset) {
+    var len = arr.length;
+    var next1 = offset;
+    var next2 = offset + 1;
+    var next3 = offset + 2;
+    if (next1 >= len || arr[next1] === 1) {
+      return;
+    }
+    arr[next1] = arr[next1] ? arr[next1] : 2;
+    if (next2 >= len || arr[next2] === 1) {
+      return;
+    }
+    arr[next2] = arr[next2] ? arr[next2] : 2;
+    if (next3 >= len || arr[next3] === 1) {
+      return;
+    }
+    arr[next3] = arr[next3] ? arr[next3] : 3;
+  }
+  var offset = 0;
+  var now;
+  var prev = 0;
+  while (offset < lines.length) {
+    now = lines[offset];
+    now =  now !== 1 ? 0 : 1;
+    if (now !== prev) {
+      if (now === 1) {
+        processLeft3(lines, offset);
+      } else if (now === 0) {
+        processRight3(lines, offset);
+      }
+    }
+    prev = now;
+    offset ++;
+  }
+  return lines;
+}
+/**
+ * printCoverageDetail
+ * @param  {Array} lines [true] 1 means no coveraged
+ * @return {}
+ */
+function printCoverageDetail(lines, source) {
+  var len = lines.length;
+  lines = processLinesMask(lines);
+  //console.log(lines);
+  for (var i = 1; i < len; i++) {
+    if (lines[i] !== 0) {
+      if (lines[i] === 3) {
+        console.log('......');
+      } else if (lines[i] === 2) {
+        echo(i, source[i - 1], false);
+      } else {
+        echo(i, source[i - 1], true);
+      }
+    }
+  }
+  function echo(lineNum, str, bool) {
+    console.log(colorful(lineNum, 'LINENUM') + '|' + colorful(str, bool ? 'YELLOW' : 'GREEN'));
+  }
+}
+/**
+ * colorful display
+ * @param  {} str
+ * @param  {} type
+ * @return {}
+ */
+function colorful(str, type) {
+  var head = '\x1B[', foot = '\x1B[0m';
+  var color = {
+    LINENUM : 36,
+    GREEN  : 32,
+    YELLOW  : 33,
+    RED : 31
+  };
+  return head + color[type] + 'm' + str + foot;
+}
\ No newline at end of file
diff --git a/reporter/html.js b/reporter/html.js
new file mode 100644
index 0000000..090622e
--- /dev/null
+++ b/reporter/html.js
@@ -0,0 +1,148 @@
+/*!
+ * jscoverage: reporter/html.js
+ * Authors  : fish <zhengxinlin at gmail.com> (https://github.com/fishbar)
+ * Create   : 2014-04-10 16:23:23
+ * CopyRight 2014 (c) Fish And Other Contributors
+ */
+
+/**
+ * Module dependencies.
+ */
+
+var fs = require('fs');
+var ejs = require('ejs');
+var path = require('path');
+
+/**
+ * Initialize a new `JsCoverage` reporter.
+ *
+ * @param {Runner} runner
+ * @param {Boolean} output
+ * @api public
+ */
+
+exports.process = function (_$jscoverage, stats, covlevel) {
+  var result = map(_$jscoverage, stats);
+  var file = __dirname + '/templates/coverage.ejs';
+  var str = fs.readFileSync(file, 'utf8').toString();
+
+  var html = ejs.render(str, {
+    debug: true,
+    cov: result,
+    coverageClass: function (n) {
+      n = n / 100;
+      if (n >= covlevel.high) {
+        return 'high';
+      }
+      if (n >= covlevel.middle) {
+        return 'medium';
+      }
+      if (n >= covlevel.low) {
+        return 'low';
+      }
+      return 'terrible';
+    },
+    filename: path.join(__dirname, './templates/cached.ejs')
+  });
+
+  fs.writeFileSync(process.cwd() + '/covreporter.html', html);
+  console.log('[REPORTER]: ', process.cwd() + '/covreporter.html');
+};
+
+/**
+ * Map jscoverage data to a JSON structure
+ * suitable for reporting.
+ *
+ * @param {Object} cov
+ * @return {Object}
+ * @api private
+ */
+
+function map(cov, stats) {
+  var ret = {
+    instrumentation: 'jscoverage', 
+    sloc: 0, 
+    hits: 0, 
+    misses: 0, 
+    coverage: 0, 
+    files: []
+  };
+
+  for (var filename in cov) {
+    if (!cov(filename).length) {
+      continue;
+    }
+    var data = coverage(filename, cov[filename], stats[filename]);
+    ret.files.push(data);
+    ret.hits += data.hits;
+    ret.misses += data.misses;
+    ret.sloc += data.sloc;
+  }
+
+  ret.files.sort(function(a, b) {
+    return a.filename.localeCompare(b.filename);
+  });
+
+  if (ret.sloc > 0) {
+    ret.coverage = (ret.hits / ret.sloc) * 100;
+  }
+
+  return ret;
+};
+
+/**
+ * Map jscoverage data for a single source file
+ * to a JSON structure suitable for reporting.
+ *
+ * @param {String} filename name of the source file
+ * @param {Object} data jscoverage coverage data
+ * @return {Object}
+ * @api private
+ */
+
+function coverage(filename, data, stats) {
+  var ret = {
+    filename: filename,
+    coverage: stats.coverage * 100,
+    hits: stats.hits,
+    misses: stats.sloc - stats.hits,
+    sloc: stats.sloc,
+    source: []
+  };
+  data.source.forEach(function(line, num){
+    num++;
+    var conds = stats.condition[num];
+    var splits = [];
+    if (conds) {
+      conds.forEach(function (v) {
+        if (!splits[v[0]]) {
+          splits[v[0]] = {start:[], end:[]};
+        }
+        if (!splits[v[0] + v[1]]) {
+          splits[v[0] + v[1]] = {start: [], end: []};
+        }
+        splits[v[0]].start.push('<i class="cond-miss">');
+        splits[v[0] + v[1]].end.push('</i>');
+      });
+      var res = [];
+      var offset = 0;
+      splits.forEach(function (v, i) {
+        if (!v) {
+          return;
+        }
+        res.push(line.substr(offset, i - offset));
+        res.push(v.end.join(''));
+        res.push(v.start.join(''));
+        offset = i;
+      });
+      res.push(line.substr(offset));
+      line = res.join('');
+    }
+    ret.source[num] = {
+      source: line,
+      coverage: data[num] === undefined ? '' : data[num],
+      condition: conds && conds.length ? true : false
+    };
+  });
+  return ret;
+}
diff --git a/reporter/summary.js b/reporter/summary.js
new file mode 100644
index 0000000..83c0004
--- /dev/null
+++ b/reporter/summary.js
@@ -0,0 +1,47 @@
+/*!
+ * jscoverage: reporter/summary.js
+ * Authors  : fish <zhengxinlin at gmail.com> (https://github.com/fishbar)
+ * Create   : 2014-04-10 16:23:23
+ * CopyRight 2014 (c) Fish And Other Contributors
+ */
+
+/**
+ * summary reporter
+ * @param  {[type]} _$jscoverage coverage object
+ * @param  {[type]} stats        coverage stats
+ * @param  {Object} covmin       coverage level {high, middle, low}
+ */
+exports.process = function (_$jscoverage, stats, covlevel) {
+  var arr = [];
+  Object.keys(stats).forEach(function (file) {
+    var msg = '[JSCOVERAGE] ' + file + ': hits[' + stats[file].hits + '], sloc[' + stats[file].sloc + '] coverage[' + stats[file].percent + ']';
+    var coverage = stats[file].coverage;
+    var type;
+    if (coverage < covlevel.low) {
+      type = 'RED';
+    } else if (coverage < covlevel.middle) {
+      type = 'YELLOW';
+    } else if (coverage < covlevel.high) {
+      type = null;
+    } else {
+      type = 'GREEN';
+    }
+    msg = colorful(msg, type);
+    arr.push(msg);
+  });
+  console.log('\n', arr.join('\n'));
+};
+
+function colorful(str, type) {
+  if (!type) {
+    return str;
+  }
+  var head = '\x1B[', foot = '\x1B[0m';
+  var color = {
+    LINENUM : 36,
+    GREEN  : 32,
+    YELLOW  : 33,
+    RED : 31
+  };
+  return head + color[type] + 'm' + str + foot;
+}
\ No newline at end of file
diff --git a/reporter/templates/coverage.ejs b/reporter/templates/coverage.ejs
new file mode 100644
index 0000000..14320bc
--- /dev/null
+++ b/reporter/templates/coverage.ejs
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Coverage</title>
+    <% include script %>
+    <% include style %>
+  </head>
+  <body>
+    <div id="coverage">
+      <h1 id="overview">jscoverage</h1>
+      <div id="menu">
+        <li><a href='#overview'>overview</a></li>
+        <% cov.files.forEach(function (file) { %>
+        <li>
+          <span class="cov <%= coverageClass(file.coverage) %>"> <%- file.coverage %></span>
+          <a href="#<%- file.filename %>">
+            <%
+              var segments = file.filename.split('/');
+              var basename = segments.pop();
+              if (segments.length) { %>
+              <span class="dirname"><%= segments.join('/') + '/' %></span>
+
+            <% } %>
+            <span class="basename"><%= basename %></span>
+          </a>
+        </li>
+        <% }); %>
+      </div>
+
+      <div id="stats" class="<%= coverageClass(cov.coverage) %>">
+        <div class="percentage"> <%= cov.coverage %>%</div>
+        <div class="sloc"> <%= cov.sloc %></div>
+        <div class="hits"> <%= cov.hits %></div>
+        <div class="misses"> <%= cov.misses %></div>
+      </div>
+      <div id="files">
+
+        <% cov.files.forEach(function (file) { %>
+        <div class="file">
+            <h2 id="<%- file.filename %>"> <%- file.filename %></h2>
+            <div id="stats" class="<%= coverageClass(file.coverage) %>">
+              <div class="percentage"> <%= file.coverage %>%</div>
+              <div class="sloc"><%- file.sloc %></div>
+              <div class="hits"><%- file.hits %></div>
+              <div class="misses"><%- file.misses %></div>
+            </div>
+            <table id="source">
+              <thead>
+                <tr>
+                  <th>Line</th>
+                  <th>Hits</th>
+                  <th>Source</th>
+                </tr>
+              </thead>
+              <tbody>
+                <% var number; %>
+                <% var line; %>
+                <% for (number = 1; number < file.source.length; number ++) { %>
+                  <% line = file.source[number];%>
+                  <% if (line.condition) { %> 
+                      <tr class="miss">
+                        <td class="line"> <%- number %></td>
+                        <td class="hits"> <%- line.coverage %></td>
+                        <td class="source"> <%- line.source %></td>
+                      </tr>
+                  <% } else if (line.coverage > 0) { %>
+                      <tr class="hit">
+                        <td class="line"> <%- number %></td>
+                        <td class="hits"> <%- line.coverage %></td>
+                        <td class="source"> <%- line.source %></td>
+                      </tr>
+                  <% } else if (0 === line.coverage) { %>
+                      <tr class="miss">
+                        <td class="line"> <%- number %></td>
+                        <td class="hits"> 0 </td>
+                        <td class="source"> <%- line.source %></td>
+                      </tr>
+                  <% } else { %>
+                      <tr>
+                        <td class="line"><%- number %></td>
+                        <td class="hits"> </td>
+                        <td class="source"><%- line.source %></td>
+                      </tr>
+                  <% } %>
+                <% } %>
+              </tbody>
+            </table>
+          </div>  
+        <% }); %>
+      </div>
+    </div>
+  </body>
+</html>
diff --git a/reporter/templates/script.ejs b/reporter/templates/script.ejs
new file mode 100644
index 0000000..073cf79
--- /dev/null
+++ b/reporter/templates/script.ejs
@@ -0,0 +1,34 @@
+<script>
+
+headings = [];
+
+onload = function(){
+  headings = document.querySelectorAll('h2');
+};
+
+onscroll = function(e){
+  var heading = find(window.scrollY);
+  if (!heading) return;
+  var links = document.querySelectorAll('#menu a')
+    , link;
+
+  for (var i = 0, len = links.length; i < len; ++i) {
+    link = links[i];
+    link.className = link.getAttribute('href') == '#' + heading.id
+      ? 'active'
+      : '';
+  }
+};
+
+function find(y) {
+  var i = headings.length
+    , heading;
+
+  while (i--) {
+    heading = headings[i];
+    if (y >= heading.offsetTop) {
+      return heading;
+    }
+  }
+}
+</script>
diff --git a/reporter/templates/style.ejs b/reporter/templates/style.ejs
new file mode 100644
index 0000000..68f9ea3
--- /dev/null
+++ b/reporter/templates/style.ejs
@@ -0,0 +1,330 @@
+<style>
+
+body {
+  font: 14px/1.6 "Helvetica Neue", Helvetica, Arial, sans-serif;
+  margin: 0;
+  color: #5d7dad;
+  border-top: 2px solid #ddd;
+}
+
+#coverage {
+  padding: 60px;
+}
+
+h1 a {
+  color: inherit;
+  font-weight: inherit;
+}
+
+h1 a:hover {
+  text-decoration: none;
+}
+
+.onload h1 {
+  opacity: 1;
+}
+
+h2 {
+  width: 80%;
+  margin-top: 80px;
+  margin-bottom: 0;
+  font-weight: 100;
+  letter-spacing: 1px;
+  border-bottom: 1px solid #eee;
+}
+
+a {
+  color: #8A6343;
+  font-weight: bold;
+  text-decoration: none;
+}
+
+a:hover {
+  text-decoration: underline;
+}
+
+ul {
+  margin-top: 20px;
+  padding: 0 15px;
+  width: 100%;
+}
+
+ul li {
+  float: left;
+  width: 40%;
+  margin-top: 5px;
+  margin-right: 60px;
+  list-style: none;
+  border-bottom: 1px solid #eee;
+  padding: 5px 0;
+  font-size: 12px;
+}
+
+ul::after {
+  content: '.';
+  height: 0;
+  display: block;
+  visibility: hidden;
+  clear: both;
+}
+
+code {
+  font: 12px monaco, monospace;
+}
+
+pre {
+  margin: 30px;
+  padding: 30px;
+  border: 1px solid #eee;
+  border-bottom-color: #ddd;
+  -webkit-border-radius: 2px;
+  -moz-border-radius: 2px;
+  border-radius: 2px;
+  -webkit-box-shadow: inset 0 0 10px #eee;
+  -moz-box-shadow: inset 0 0 10px #eee;
+  box-shadow: inset 0 0 10px #eee;
+  overflow-x: auto;
+}
+
+img {
+  margin: 30px;
+  padding: 1px;
+  -webkit-border-radius: 3px;
+  -moz-border-radius: 3px;
+  border-radius: 3px;
+  -webkit-box-shadow: 0 3px 10px #dedede, 0 1px 5px #888;
+  -moz-box-shadow: 0 3px 10px #dedede, 0 1px 5px #888;
+  box-shadow: 0 3px 10px #dedede, 0 1px 5px #888;
+  max-width: 100%;
+}
+
+footer {
+  background: #eee;
+  width: 100%;
+  padding: 50px 0;
+  text-align: right;
+  border-top: 1px solid #ddd;
+}
+
+footer span {
+  display: block;
+  margin-right: 30px;
+  color: #888;
+  font-size: 12px;
+}
+
+#menu {
+  position: fixed;
+  font-size: 12px;
+  overflow-y: auto;
+  top: 0;
+  right: 0;
+  margin: 0;
+  height: 100%;
+  padding: 15px 0;
+  text-align: left;
+  border-left: 1px solid #eee;
+  -moz-box-shadow: 0 0 2px #888
+     , inset 5px 0 20px rgba(0,0,0,.5)
+     , inset 5px 0 3px rgba(0,0,0,.3);
+  -webkit-box-shadow: 0 0 2px #888
+     , inset 5px 0 20px rgba(0,0,0,.5)
+     , inset 5px 0 3px rgba(0,0,0,.3);
+  box-shadow: 0 0 2px #888
+     , inset 5px 0 20px rgba(0,0,0,.5)
+     , inset 5px 0 3px rgba(0,0,0,.3);
+  -webkit-font-smoothing: antialiased;
+  background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGYAAABmCAMAAAAOARRQAAABelBMVEUjJSU6OzshIyM5OjoqKy02NjgsLS01NTYjJCUzNTUgISMlJSc0NTUvMDA6PDwlJyg1NjYoKis2NjYrLS02ODkpKyw0NDYrLC04ODovLzA4Ojo0NDUtLy86OjwjIyU4OTosLS82ODgtLS8hIyQvMTEnKCooKSsrKy0qLCwkJSUnKCkrLCwpKiwwMjIxMzMqLC0tLS0pKissLC00NTYwMDIwMTQpKysoKSovMDEtLzA2OTkxMzUrKywvLy8qKyszNTY5OzsqKiw6OjswMDExNDUoKiozNDUvMDIyNDY1Njg2Njk5OTozMzU0NjY4ODkiIyUiIyQ4OTkuMDEmKCowMjQwMTErLS4qKywwMTMhIiMpKiopKy0tLjAkJScxNDQvLzExNDYyNDQmKCk [...]
+}
+
+#menu::after {
+  display: block;
+  content: '';
+  padding-top: 80px;
+}
+
+#logo {
+  position: fixed;
+  bottom: 10px;
+  right: 10px;
+  background: rgba(255,255,255,.1);
+  font-size: 11px;
+  display: block;
+  width: 20px;
+  height: 20px;
+  line-height: 20px;
+  text-align: center;
+  -webkit-border-radius: 20px;
+  -moz-border-radius: 20px;
+  border-radius: 20px;
+  -webkit-box-shadow: 0 0 3px rgba(0,0,0,.2);
+  -moz-box-shadow: 0 0 3px rgba(0,0,0,.2);
+  box-shadow: 0 0 3px rgba(0,0,0,.2);
+  color: inherit;
+}
+
+#menu li a {
+  display: block;
+  color: white;
+  padding: 0 35px 0 25px;
+  -webkit-transition: background 300ms;
+  -moz-transition: background 300ms;
+}
+
+#menu li {
+  position: relative;
+  list-style: none;
+}
+
+#menu a:hover,
+#menu a.active {
+  text-decoration: none;
+  background: rgba(255,255,255,.1);
+}
+
+#menu li:hover .cov {
+  opacity: 1;
+}
+
+#menu li .dirname {
+  opacity: .60;
+  padding-right: 2px;
+}
+
+#menu li .basename {
+  opacity: 1;
+}
+
+#menu .cov {
+  background: rgba(200,200,200, 1);
+  position: absolute;
+  top: 0;
+  right: 8px;
+  font-size: 9px;
+  opacity: .6;
+  text-align: left;
+  width: 17px;
+  -webkit-border-radius: 10px;
+  -moz-border-radius: 10px;
+  border-radius: 10px;
+  padding: 1px 2px;
+  text-align: center;
+}
+
+#stats:nth-child(2n) {
+  display: inline-block;
+  margin-top: 15px;
+  border: 1px solid #eee;
+  padding: 10px;
+  -webkit-box-shadow: inset 0 0 2px #eee;
+  -moz-box-shadow: inset 0 0 2px #eee;
+  box-shadow: inset 0 0 2px #eee;
+  -webkit-border-radius: 5px;
+  -moz-border-radius: 5px;
+  border-radius: 5px;
+}
+
+#stats div {
+  float: left;
+  padding: 0 5px;
+}
+
+#stats::after {
+  display: block;
+  content: '';
+  clear: both;
+}
+
+#stats .sloc::after {
+  content: ' SLOC';
+  color: #b6b6b6;
+}
+
+#stats .percentage::after {
+  content: ' coverage';
+  color: #b6b6b6;
+}
+
+#stats .hits {
+  display: none;
+}
+#stats .misses:after {
+  content: ' MISSES';
+  color: #b6b6b6;
+}
+
+.high {
+  color: #00d4b4;
+}
+.medium {
+  color: #e87d0d;
+}
+.low {
+  color: #d4081a;
+}
+.terrible {
+  color: #d4081a;
+  font-weight: bold;
+}
+
+table {
+  width: 80%;
+  margin-top: 10px;
+  border-collapse: collapse;
+  border: 1px solid #cbcbcb;
+  color: #363636;
+  -webkit-border-radius: 3px;
+  -moz-border-radius: 3px;
+  border-radius: 3px;
+}
+
+table thead {
+  display: none;
+}
+
+table td.line,
+table td.hits {
+  width: 20px;
+  background: #eaeaea;
+  text-align: center;
+  font-size: 11px;
+  padding: 0 10px;
+  color: #949494;
+}
+
+table td.hits {
+  width: 10px;
+  padding: 2px 5px;
+  color: rgba(0,0,0,.2);
+  background: #f0f0f0;
+}
+
+tr.miss td.line,
+tr.miss td.hits {
+  background: #e6c3c7;
+}
+
+tr.miss td {
+  background: #f8d5d8;
+}
+
+td.source {
+  padding-left: 15px;
+  line-height: 15px;
+  white-space: pre;
+  font: 12px monaco, monospace;
+}
+
+code .comment { color: #ddd }
+code .init { color: #2F6FAD }
+code .string { color: #5890AD }
+code .keyword { color: #8A6343 }
+code .number { color: #2F6FAD }
+
+i.cond-miss{
+  background-color:#ff6464;
+  font-style:normal;
+  border-radius: 4px;
+  padding: 0px 2px;
+}
+</style>
\ No newline at end of file
diff --git a/test/abc.js b/test/abc.js
new file mode 100644
index 0000000..a0616de
--- /dev/null
+++ b/test/abc.js
@@ -0,0 +1,51 @@
+/*!
+ * jscoverage: test/abc.js
+ * Authors  : fish <zhengxinlin at gmail.com> (https://github.com/fishbar)
+ * Create   : 2014-04-10 16:23:23
+ * CopyRight 2014 (c) Fish And Other Contributors
+ */
+var cde = require('./cde');
+var a = 1;
+var b = 2;
+var c = 3;
+var d;
+var e = a > 1 ? 1 : 2;
+
+var reset = {
+  abc:function () {}
+};
+
+function abc() {
+  var tmp = a + b;
+  var t = 1;
+  // test require ok
+  cde.a();
+  // test switch coverage
+  testSwitch('first');
+  /* @covignore */
+  testSwitch('second');
+  testSwitch();
+  return tmp + c;
+}
+
+function testSwitch(act) {
+  var res = [
+    'a',
+    'b',
+    'c'
+  ];
+  var tmp;
+  switch (act) {
+  case 'first' :
+    tmp = res[0];
+    break;
+  case 'second' :
+    tmp = res[1];
+    break;
+  default:
+    tmp = res.join(',');
+  }
+  return tmp;
+}
+abc();
+exports.abc = abc;
diff --git a/test/cde.js b/test/cde.js
new file mode 100644
index 0000000..e8b9cff
--- /dev/null
+++ b/test/cde.js
@@ -0,0 +1,15 @@
+/*!
+ * jscoverage: test/cde.js
+ * Authors  : fish <zhengxinlin at gmail.com> (https://github.com/fishbar)
+ * Create   : 2014-04-10 16:23:23
+ * CopyRight 2014 (c) Fish And Other Contributors
+ */
+function a(){
+  var a = 1;
+  var b = 2;
+  if (a || b > a) {
+    console.log(a);
+  }
+  return a+b;
+}
+exports.a = a;
\ No newline at end of file
diff --git a/test/dir/a/a2 b/test/dir/a/a2
new file mode 100644
index 0000000..c68ea98
--- /dev/null
+++ b/test/dir/a/a2
@@ -0,0 +1,3 @@
+function a2(){
+  return "this is function a2";
+}
\ No newline at end of file
diff --git a/test/dir/a/test.md b/test/dir/a/test.md
new file mode 100644
index 0000000..3f751ae
--- /dev/null
+++ b/test/dir/a/test.md
@@ -0,0 +1 @@
+var str = "this is a test file";
diff --git a/test/dir/a1.js b/test/dir/a1.js
new file mode 100644
index 0000000..8e5c44f
--- /dev/null
+++ b/test/dir/a1.js
@@ -0,0 +1,3 @@
+function a1(){
+  return "this is function a1";
+}
\ No newline at end of file
diff --git a/test/example.js b/test/example.js
new file mode 100644
index 0000000..58799e9
--- /dev/null
+++ b/test/example.js
@@ -0,0 +1,120 @@
+/*!
+ * jscoverage: example.js
+ * Authors  : fish <zhengxinlin at gmail.com> (https://github.com/fishbar)
+ * Create   : 2014-04-03 15:20:13
+ * CopyRight 2014 (c) Fish And Other Contributors
+ */
+var expect = require('expect.js');
+
+exports.testTryCatch = function (a) {
+  try {
+    return a.run();
+  } catch (e) {
+    return 'catch error';
+  }
+};
+
+exports.testIfElse = function (a, b, c, d) {
+  if (a > 0 && b > 0) {
+    return 'ab';
+  } else if (c || d) {
+    return 'cd';
+  } else {
+    return 'unknow';
+  }
+};
+
+exports.testCondition = function (a, b ,c) {
+  return a || b > c ? '1' : '2';
+};
+
+exports.testWhile = function () {
+  var a = 0;
+  var res = '';
+  while(a < 2) {
+    res += 'a';
+    a ++;
+  }
+  var b = 0;
+  do {
+    res += 'b';
+    b ++;
+  } while (b < 2);
+  return res;
+};
+
+
+describe('example.js', function () {
+  it('test try catch', function () {
+    expect(exports.testTryCatch({})).to.be.match(/catch\ error/);
+    expect(exports.testTryCatch({run: function () {return 'run';}})).to.be('run');
+  });
+
+  it('test if else', function () {
+    expect(exports.testIfElse(1, 0)).to.be('unknow');
+    expect(exports.testIfElse(1, 1)).to.be('ab');
+    expect(exports.testIfElse(0, 0, 1, 0)).to.be('cd');
+  });
+
+  it('test condition', function () {
+    expect(exports.testCondition(1)).to.be('1');
+    expect(exports.testCondition(0, 2, 1)).to.be('1');
+    expect(exports.testCondition(0, 0, 0)).to.be('2');
+  });
+
+  it('test while', function () {
+    expect(exports.testWhile()).to.be('aabb');
+  });
+
+  it('test switch', function () {
+    expect(exports.testSwitch('a')).to.be('a');
+    expect(exports.testSwitch('b')).to.be('b');
+    expect(exports.testSwitch('c')).to.be('c');
+    expect(exports.testSwitch('0')).to.be('d');
+  });
+
+  it('test for', function () {
+    expect(exports.testFor()).to.be(1);
+  });
+
+  it('test binary', function () {
+    expect(exports.testBinary('a')).to.be('a');
+    expect(exports.testBinary(null, 'b')).to.be('b');
+    expect(exports.testBinary(null, null, null, 'b')).to.be('b');
+    expect(exports.testBinary(null, null, 'b')).to.be('b');
+  });
+});
+
+exports.testSwitch = function (a) {
+  var res = null;
+  switch (a) {
+    case 'a':
+      return 'a';
+    case 'b':
+      return 'b';
+    case 'c':
+      res = 'c';
+      break;
+    default:
+      res = 'd';
+  }
+  return res;
+};
+
+exports.testFor = function () {
+  var a = 0;
+  for (var i = 0; i < 3; i++) {
+    if (i > 1) {
+      a ++;
+    }
+  }
+  return a;
+};
+exports.testBinary = function (a, b, c, d) {
+  return a || b || c || d;
+};
+// anonymous function
+(function(){
+  var a = 1;
+  console.log('this line should covered');
+})();
\ No newline at end of file
diff --git a/test/index.js b/test/index.js
new file mode 100644
index 0000000..008d703
--- /dev/null
+++ b/test/index.js
@@ -0,0 +1,165 @@
+/*!
+ * jscoverage: test/index.js
+ * Authors  : fish <zhengxinlin at gmail.com> (https://github.com/fishbar)
+ * Create   : 2014-04-03 15:20:13
+ * CopyRight 2014 (c) Fish And Other Contributors
+ */
+var fs = require('xfs');
+var path = require('path');
+var expect = require('expect.js');
+var Module = require('module');
+var index = require('../index');
+
+index.config({
+  get: '_get',
+  call: '$$call',
+  replace: '_replace',
+  reset: '_reset'
+});
+
+var abc = require('./abc');
+
+describe("index.js", function () {
+  describe("exports.processFile", function () {
+    it('should return an jsc convert file', function () {
+      var source = path.join(__dirname, './abc.js');
+      var dest = path.join(__dirname, './abc.cov.js');
+      index.processFile(source, dest);
+      expect(fs.existsSync(dest)).to.be(true);
+      expect(fs.readFileSync(dest)).to.match(/_\$jscoverage/);
+      fs.unlinkSync(dest);
+    });
+    it('should throw error when path not file', function () {
+      var source = path.join(__dirname, './dir');
+      var dest = path.join(__dirname, './abc.cov.js');
+      var err;
+      try {
+        index.processFile(source, dest);
+      } catch (e) {
+        err = e;
+      }
+      expect(err.message).to.match(/path is dir/);
+    });
+    it('should throw error when path is a socket file', function (done) {
+      var net = require('net');
+      var serv = net.createServer(function(client){});
+      var ff = path.join(__dirname, './dir/a.sock');
+      serv.listen(ff, function (err) {
+        try {
+          index.processFile(ff, path.join(__dirname, './dir/sock-cov'));
+        } catch (e) {
+          expect(e.message).to.match(/not a regular file/);
+        }
+        serv.close(done);
+      });
+    });
+    /*
+    it('should return an jsc convert dir', function (done) {
+      var source = path.join(__dirname, './dir');
+      var dest = path.join(__dirname, './dir-cov');
+      index.processFile(source, dest);
+      expect(fs.existsSync(dest)).to.be(true);
+      expect(fs.readFileSync(dest + '/a1.js')).to.match(/_\$jscoverage/);
+      expect(fs.readFileSync(dest + '/a/a2')).to.match(/_\$jscoverage/);
+      fs.rmdir(dest, function () {
+        done();
+      });
+    });
+    it('should ignore exclude', function (done) {
+      var source = path.join(__dirname, './dir');
+      var dest = path.join(__dirname, './dir-cov');
+      index.processFile(source, dest, ['a2', /\.md$/i]);
+      expect(fs.existsSync(dest + '/a/a2')).to.not.ok();
+      fs.rmdir(dest, function () {
+        done();
+      });
+    });
+    */
+    it('should throw error when source and dest not currect', function () {
+      try {
+        index.processFile();
+      } catch (e) {
+        expect(e.message).to.match(/path must be a string/);
+      }
+    });
+    it('should throw error when source and dest not currect', function () {
+      function _empty() {
+        index.processFile('./abc', '.abc.cov');
+      }
+      expect(_empty).to.throwException(/no such file or directory/);
+    });
+    it('should throw error when source and dest not currect', function () {
+      function _empty() {
+        index.processFile(path.join(__dirname, './abdc.js'), '/tmp/abc.cov.js');
+      }
+      expect(_empty).to.throwException(/no such file or directory/);
+    });
+  });
+
+  
+
+  describe('test Module.extension[".js"]', function () {
+    it('should return a function', function (done) {
+      var module = {
+        _compile: function (content, filename) {
+          var ff = new Function ('require', 'module', 'exports', '__dirname', '__filename', content + ';return module.exports;');
+          var module = {exports: {}};
+          var mo = ff(require, module, module.exports, __dirname, filename);
+          mo._replace('d', [
+            undefined,
+            null,
+            1,
+            NaN,
+            'string',
+            [1, 2, 3],
+            {'abc': [1, 2, 3]},
+            /a\\\\b/g
+          ]);
+          var res = mo._get('d');
+          expect(res[0]).to.be(undefined);
+          expect(res[1]).to.be(null);
+          expect(res[2]).to.be(1);
+          expect(isNaN(res[3])).to.be.ok();
+          expect(res[7].test('a\\\\bc')).to.be.ok();
+          mo._reset();
+          expect(mo._get('d')).to.be(undefined);
+          done();
+        }
+      };
+      Module._extensions['.js'](module, path.join(__dirname, './abc.js'), {
+        needjsc : true,
+        flagjsc : true,
+        needinject : true
+      });
+    });
+    it('should return a function', function (done) {
+      var module = {
+        _compile: function (content, filename) {
+          var ff = new Function ('require', 'module', 'exports', '__dirname', '__filename', content + ';return module.exports;');
+          var module = {exports: {}};
+          var mo = ff(require, module, module.exports, __dirname, filename);
+          mo._replace('d', {});
+          var res = mo._get('d');
+          expect(res).to.be.eql({});
+          mo._reset();
+          expect(mo._get('d')).to.be(undefined);
+          done();
+        }
+      };
+      Module._extensions['.js'](module, path.join(__dirname, './abc.js'), {
+        needjsc : true,
+        flagjsc : true,
+        needinject : true
+      });
+    });
+  });
+  
+  describe('getLCOV', function () {
+    it('should be ok', function () {
+      var res = index.getLCOV();
+      expect(res).to.match(/end_of_record/);
+      expect(res).to.match(/SF:/);
+      expect(res).to.match(/DA:\d+,\d+/);
+    });
+  });
+});
diff --git a/test/jscoverage.js b/test/jscoverage.js
new file mode 100644
index 0000000..df7964a
--- /dev/null
+++ b/test/jscoverage.js
@@ -0,0 +1,43 @@
+/*!
+ * jscoverage: test/jscoverage.js
+ * Authors  : fish <zhengxinlin at gmail.com> (https://github.com/fishbar)
+ * Create   : 2014-04-08 19:32:38
+ * CopyRight 2014 (c) Fish And Other Contributors
+ */
+var jscoverage = require('../lib/jscoverage');
+var expect = require('expect.js');
+jscoverage.__coveraged__ = false;
+jscoverage = require('../lib/jscoverage');
+
+var func = jscoverage._get('jscFunctionBody').toString().split(/\n/);
+func.shift();
+func.pop();
+
+var wrapperGlobal = eval('(function(){ var global = {}; var $lines$ = [1]; var $conds$ = {"1_1_1": 0}; var $source$=["var a = a ? 1: 0;"];' + func.join('\n') + ' return global;})');
+
+var _global = wrapperGlobal();
+
+describe('lib/jscoverage.js', function(){
+  it('jscFunctionBody shoud be ok', function(){
+    // mark line
+    _global._$jscmd('$file$', 'line', 1);
+    // mark cond
+    _global._$jscmd('$file$', 'cond', '1_1_1', '');
+    expect(_global._$jscoverage['$file$'][1]).to.be(1);
+    expect(_global._$jscoverage['$file$'].condition['1_1_1']).to.eql(1);
+  });
+
+  it('shoud return "" when content empty', function () {
+    var res = jscoverage.process('abc', '');
+    expect(res).to.be('');
+  });
+  it('shoud throw error when filename empty', function () {
+    var err;
+    try {
+      jscoverage.process(null, '');
+    } catch (e) {
+      err = e;
+    }
+    expect(err.message).to.match(/filename needed!/);
+  });
+});
\ No newline at end of file
diff --git a/test/patch.js b/test/patch.js
new file mode 100644
index 0000000..d732ef2
--- /dev/null
+++ b/test/patch.js
@@ -0,0 +1,30 @@
+/*!
+ * jscoverage: test/patch.js
+ * Authors  : fish <zhengxinlin at gmail.com> (https://github.com/fishbar)
+ * Create   : 2014-04-10 16:23:23
+ * CopyRight 2014 (c) Fish And Other Contributors
+ */
+var patch = require('../lib/patch');
+var expect = require('expect.js');
+var checkModule = patch._get('checkModule');
+
+describe('patch.js', function () {
+  describe('checkModule()', function () {
+    it('should return false when native module', function () {
+      expect(checkModule('fs')).to.be(false);
+    });
+    it('should return false when /node_modules/ module', function () {
+      expect(checkModule(process.cwd() + '/node_modules/fs')).to.be(false);
+    });
+    it('should return false when native module', function () {
+      expect(checkModule('fs')).to.be(false);
+    });
+    it('should return true when ignore no match', function () {
+      expect(checkModule('/abc/def')).to.be(true);
+    });
+    it('should return true when ignore no match', function () {
+      patch.setCovIgnore([/\/tet\/a.js/]);
+      expect(checkModule('/tet/a.js')).to.be(false);
+    });
+  });
+});
\ No newline at end of file
diff --git a/test/reporter_detail.js b/test/reporter_detail.js
new file mode 100644
index 0000000..2107d57
--- /dev/null
+++ b/test/reporter_detail.js
@@ -0,0 +1,38 @@
+/*!
+ * jscoverage: test/reporter_detail.js
+ * Authors  : fish <zhengxinlin at gmail.com> (https://github.com/fishbar)
+ * Create   : 2014-04-10 16:23:23
+ * CopyRight 2014 (c) Fish And Other Contributors
+ */
+var expect = require('expect.js');
+var testMod = require('../reporter/detail');
+describe('exports.processLinesMask', function () {
+  it('should be ok when test1', function () {
+    var process = testMod._get('processLinesMask');
+    var input  = [0, 0, 0, 1, 1, 0, 0, 1, 0, 0];
+    var result = [3, 2, 2, 1, 1, 2, 2, 1, 2, 2];
+    expect(process(input)).to.be.eql(result);
+  });
+  it('should be ok when test2', function () {
+    var process = testMod._get('processLinesMask');
+    var input  = [0, 0, 1, 1, 0, 0, 1, 0, 1];
+    var result = [2, 2, 1, 1, 2, 2, 1, 2, 1];
+    expect(process(input)).to.be.eql(result);
+  });
+  it('should be ok when test3', function () {
+    var process = testMod._get('processLinesMask');
+    var input  = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0];
+    var result = [2, 2, 1, 2, 2, 3, 0, 0, 0, 3, 2, 2, 1, 2];
+    expect(process(input)).to.be.eql(result);
+  });
+  it('should be ok when test4', function () {
+    var process = testMod._get('processLinesMask');
+    var input  = [0];
+    var result = [0];
+    expect(process(input)).to.be.eql(result);
+  });
+
+  it('should be ok', function () {
+    testMod.process(_$jscoverage, {}, {high: 90, middle: 70, low: 20});
+  });
+});
\ No newline at end of file

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-jscoverage.git



More information about the Pkg-javascript-commits mailing list