ffdadd9c31699d16f703f651efa117abe71bce96.svn-base 3.08 KB
/**
 * Module dependencies.
 */

var Base = require('./base')
  , utils = require('../utils')
  , fs = require('fs')
  , escape = utils.escape;

/**
 * Save timer references to avoid Sinon interfering (see GH-237).
 */

var Date = global.Date
  , setTimeout = global.setTimeout
  , setInterval = global.setInterval
  , clearTimeout = global.clearTimeout
  , clearInterval = global.clearInterval;

/**
 * Expose `XUnit`.
 */

exports = module.exports = XUnit;

/**
 * Initialize a new `XUnit` reporter.
 *
 * @param {Runner} runner
 * @api public
 */

function XUnit(runner, options) {
  Base.call(this, runner);
  var stats = this.stats
    , tests = []
    , self = this;

  if (options.reporterOptions && options.reporterOptions.output) {
      if (! fs.createWriteStream) {
          throw new Error('file output not supported in browser');
      }
      self.fileStream = fs.createWriteStream(options.reporterOptions.output);
  }

  runner.on('pending', function(test){
    tests.push(test);
  });

  runner.on('pass', function(test){
    tests.push(test);
  });

  runner.on('fail', function(test){
    tests.push(test);
  });

  runner.on('end', function(){
    self.write(tag('testsuite', {
        name: 'TAP Tests'
      , tests: stats.tests
      , failures: stats.failures
      , errors: stats.failures
      , skipped: stats.tests - stats.failures - stats.passes
      , timestamp: (new Date).toUTCString()
      , time: (stats.duration / 1000) || 0
    }, false));

    tests.forEach(function(t) { self.test(t); });
    self.write('</testsuite>');
  });
}

/**
 * Override done to close the stream (if it's a file).
 */
XUnit.prototype.done = function(failures, fn) {
    if (this.fileStream) {
        this.fileStream.end(function() {
            fn(failures);
        });
    } else {
        fn(failures);
    }
};

/**
 * Inherit from `Base.prototype`.
 */

XUnit.prototype.__proto__ = Base.prototype;

/**
 * Write out the given line
 */
XUnit.prototype.write = function(line) {
    if (this.fileStream) {
        this.fileStream.write(line + '\n');
    } else {
        console.log(line);
    }
};

/**
 * Output tag for the given `test.`
 */

XUnit.prototype.test = function(test, ostream) {
  var attrs = {
      classname: test.parent.fullTitle()
    , name: test.title
    , time: (test.duration / 1000) || 0
  };

  if ('failed' == test.state) {
    var err = test.err;
    this.write(tag('testcase', attrs, false, tag('failure', {}, false, cdata(escape(err.message) + "\n" + err.stack))));
  } else if (test.pending) {
    this.write(tag('testcase', attrs, false, tag('skipped', {}, true)));
  } else {
    this.write(tag('testcase', attrs, true) );
  }
};

/**
 * HTML tag helper.
 */

function tag(name, attrs, close, content) {
  var end = close ? '/>' : '>'
    , pairs = []
    , tag;

  for (var key in attrs) {
    pairs.push(key + '="' + escape(attrs[key]) + '"');
  }

  tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end;
  if (content) tag += content + '</' + name + end;
  return tag;
}

/**
 * Return cdata escaped CDATA `str`.
 */

function cdata(str) {
  return '<![CDATA[' + escape(str) + ']]>';
}