From 1bcaade0d49740dd73d5352d3ee9cb4d5fc904ad Mon Sep 17 00:00:00 2001 From: Evan Cordell Date: Wed, 23 Oct 2013 18:11:01 -0500 Subject: [PATCH] Adds /lib --- lib/blueprint-ast-to-runtime.js | 78 ++++++++++ lib/cli-reporter.js | 57 +++++++ lib/configure-reporters.js | 20 +++ lib/dredd.js | 126 +++++++++++++++ lib/example-to-http-payload-pair.js | 52 +++++++ lib/execute-transaction.js | 173 +++++++++++++++++++++ lib/expand-uri-template-with-parameters.js | 81 ++++++++++ lib/flatten-headers.js | 14 ++ lib/inherit-headers.js | 15 ++ lib/inherit-parameters.js | 15 ++ lib/logger.js | 41 +++++ lib/reporter.js | 71 +++++++++ lib/x-unit-reporter.js | 110 +++++++++++++ 13 files changed, 853 insertions(+) create mode 100644 lib/blueprint-ast-to-runtime.js create mode 100644 lib/cli-reporter.js create mode 100644 lib/configure-reporters.js create mode 100644 lib/dredd.js create mode 100644 lib/example-to-http-payload-pair.js create mode 100644 lib/execute-transaction.js create mode 100644 lib/expand-uri-template-with-parameters.js create mode 100644 lib/flatten-headers.js create mode 100644 lib/inherit-headers.js create mode 100644 lib/inherit-parameters.js create mode 100644 lib/logger.js create mode 100644 lib/reporter.js create mode 100644 lib/x-unit-reporter.js diff --git a/lib/blueprint-ast-to-runtime.js b/lib/blueprint-ast-to-runtime.js new file mode 100644 index 000000000..380cece3d --- /dev/null +++ b/lib/blueprint-ast-to-runtime.js @@ -0,0 +1,78 @@ +// Generated by CoffeeScript 1.6.3 +var blueprintAstToRuntime, exampleToHttpPayloadPair, expandUriTemplateWithParameters, inheritHeaders, inheritParameters; + +inheritHeaders = require('./inherit-headers'); + +inheritParameters = require('./inherit-parameters'); + +expandUriTemplateWithParameters = require('./expand-uri-template-with-parameters'); + +exampleToHttpPayloadPair = require('./example-to-http-payload-pair'); + +blueprintAstToRuntime = function(blueprintAst) { + var action, example, message, origin, resource, resourceGroup, result, runtime, transaction, uriResult, _i, _j, _k, _l, _len, _len1, _len2, _len3, _len4, _len5, _len6, _m, _n, _o, _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6; + runtime = { + transactions: [], + errors: [], + warnings: [] + }; + origin = {}; + _ref = blueprintAst['resourceGroups']; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + resourceGroup = _ref[_i]; + origin['resourceGroupName'] = resourceGroup['name']; + _ref1 = resourceGroup['resources']; + for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { + resource = _ref1[_j]; + origin['resourceName'] = resource['name']; + _ref2 = resource['actions']; + for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { + action = _ref2[_k]; + origin['actionName'] = action['name']; + action['headers'] = inheritHeaders(action['headers'], resource['headers']); + action['parameters'] = inheritParameters(action['parameters'], resource['parameters']); + uriResult = expandUriTemplateWithParameters(resource['uriTemplate'], action['parameters']); + _ref3 = uriResult['warnings']; + for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) { + message = _ref3[_l]; + runtime['warnings'].push({ + origin: JSON.parse(JSON.stringify(origin)), + message: message + }); + } + _ref4 = uriResult['errors']; + for (_m = 0, _len4 = _ref4.length; _m < _len4; _m++) { + message = _ref4[_m]; + runtime['errors'].push({ + origin: JSON.parse(JSON.stringify(origin)), + message: message + }); + } + if (uriResult['uri'] !== null) { + _ref5 = action['examples']; + for (_n = 0, _len5 = _ref5.length; _n < _len5; _n++) { + example = _ref5[_n]; + origin['exampleName'] = example['name']; + result = exampleToHttpPayloadPair(example, action['headers']); + _ref6 = result['warnings']; + for (_o = 0, _len6 = _ref6.length; _o < _len6; _o++) { + message = _ref6[_o]; + runtime['warnings'].push({ + origin: JSON.parse(JSON.stringify(origin)), + message: message + }); + } + transaction = result['pair']; + transaction['origin'] = JSON.parse(JSON.stringify(origin)); + transaction['request']['uri'] = uriResult['uri']; + transaction['request']['method'] = action['method']; + runtime['transactions'].push(transaction); + } + } + } + } + } + return runtime; +}; + +module.exports = blueprintAstToRuntime; diff --git a/lib/cli-reporter.js b/lib/cli-reporter.js new file mode 100644 index 000000000..cb7ae4a29 --- /dev/null +++ b/lib/cli-reporter.js @@ -0,0 +1,57 @@ +// Generated by CoffeeScript 1.6.3 +var CliReporter, Reporter, logger, + __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + +logger = require('./logger'); + +Reporter = require('./reporter'); + +CliReporter = (function(_super) { + __extends(CliReporter, _super); + + function CliReporter(path) { + this.createReport = __bind(this.createReport, this); + this.addTest = __bind(this.addTest, this); + CliReporter.__super__.constructor.call(this); + this.type = "cli"; + } + + CliReporter.prototype.addTest = function(test, callback) { + CliReporter.__super__.addTest.call(this, test, function(error) { + if (error) { + return callback(error); + } + }); + switch (test.status) { + case 'pass': + logger.pass(test.title); + break; + case 'fail': + logger.fail(test.title); + logger.fail(test.message); + logger.request("\n" + (JSON.stringify(test.request, null, 4)) + "\n"); + logger.expected("\n" + (JSON.stringify(test.expected, null, 4)) + "\n"); + logger.actual("\n" + (JSON.stringify(test.actual, null, 4)) + "\n\n"); + } + return callback(); + }; + + CliReporter.prototype.createReport = function(callback) { + CliReporter.__super__.createReport.call(this, function(error) { + if (error) { + return callback(error); + } + }); + if (this.stats.tests > 0) { + logger.complete("" + this.stats.passes + " passing, " + this.stats.failures + " failing, " + (this.stats.tests - this.stats.failures - this.stats.passes) + " skipped"); + } + return callback(); + }; + + return CliReporter; + +})(Reporter); + +module.exports = CliReporter; diff --git a/lib/configure-reporters.js b/lib/configure-reporters.js new file mode 100644 index 000000000..2c68f3037 --- /dev/null +++ b/lib/configure-reporters.js @@ -0,0 +1,20 @@ +// Generated by CoffeeScript 1.6.3 +var CliReporter, Reporter, XUnitReporter, configureReporters; + +Reporter = require('./reporter'); + +XUnitReporter = require('./x-unit-reporter'); + +CliReporter = require('./cli-reporter'); + +configureReporters = function(config) { + config.reporter = new Reporter(); + if (!config.options.silent) { + config.reporter.addReporter(new CliReporter); + } + if (config.options.reporter === 'junit') { + return config.reporter.addReporter(new XUnitReporter(config.options.output)); + } +}; + +module.exports = configureReporters; diff --git a/lib/dredd.js b/lib/dredd.js new file mode 100644 index 000000000..01ef1d768 --- /dev/null +++ b/lib/dredd.js @@ -0,0 +1,126 @@ +// Generated by CoffeeScript 1.6.3 +var Dredd, async, blueprintAstToRuntime, cli, configureReporters, executeTransaction, fs, options, protagonist, + __hasProp = {}.hasOwnProperty; + +async = require('async'); + +fs = require('fs'); + +protagonist = require('protagonist'); + +cli = require('cli'); + +executeTransaction = require('./execute-transaction'); + +blueprintAstToRuntime = require('./blueprint-ast-to-runtime'); + +configureReporters = require('./configure-reporters'); + +options = { + 'dry-run': ['d', 'Run without performing tests.'], + silent: ['s', 'Suppress all command line output'], + reporter: ['r', 'Output additional report format. Options: junit', 'string'], + output: ['o', 'Specifies output file when using additional reporter', 'file'], + debug: [null, 'Display debug information'] +}; + +Dredd = (function() { + var configuredTransactions, handleRuntimeProblems; + + function Dredd(config) { + var key, value; + this.configuration = { + blueprintPath: null, + server: null, + reporter: null, + request: null, + options: { + 'dry-run': false, + silent: false, + reporter: null, + output: null, + debug: false + } + }; + for (key in config) { + if (!__hasProp.call(config, key)) continue; + value = config[key]; + this.configuration[key] = value; + } + configureReporters(this.configuration); + } + + Dredd.prototype.run = function(callback) { + var config; + config = this.configuration; + return fs.readFile(config.blueprintPath, 'utf8', function(error, data) { + if (error) { + return callback(error); + } + return protagonist.parse(data, function(error, result) { + var runtime, runtimeError; + if (error) { + return callback(error); + } + runtime = blueprintAstToRuntime(result['ast']); + runtimeError = handleRuntimeProblems(runtime); + if (runtimeError) { + return callback(runtimeError); + } + return async.eachSeries(configuredTransactions(runtime, config), executeTransaction, function(error) { + if (error) { + return callback(error); + } + config.reporter.createReport(function(error) { + if (error) { + return callback(error); + } + }); + return callback(); + }); + }); + }); + }; + + handleRuntimeProblems = function(runtime) { + var error, message, origin, warning, _i, _j, _len, _len1, _ref, _ref1; + if (runtime['warnings'].length > 0) { + _ref = runtime['warnings']; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + warning = _ref[_i]; + message = warning['message']; + origin = warning['origin']; + cli.info("Runtime compilation warning: " + warning['message'] + "\n on " + origin['resourceGroupName'] + ' > ' + origin['resourceName'] + ' > ' + origin['actionName']); + } + } + if (runtime['errors'].length > 0) { + _ref1 = runtime['errors']; + for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { + error = _ref1[_j]; + message = error['message']; + origin = error['origin']; + cli.error("Runtime compilation error: " + error['message'] + "\n on " + origin['resourceGroupName'] + ' > ' + origin['resourceName'] + ' > ' + origin['actionName']); + } + return new Error("Error parsing ast to blueprint."); + } + }; + + configuredTransactions = function(runtime, config) { + var transaction, transactionsWithConfiguration, _i, _len, _ref; + transactionsWithConfiguration = []; + _ref = runtime['transactions']; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + transaction = _ref[_i]; + transaction['configuration'] = config; + transactionsWithConfiguration.push(transaction); + } + return transactionsWithConfiguration; + }; + + return Dredd; + +})(); + +module.exports = Dredd; + +module.exports.options = options; diff --git a/lib/example-to-http-payload-pair.js b/lib/example-to-http-payload-pair.js new file mode 100644 index 000000000..083b0d57a --- /dev/null +++ b/lib/example-to-http-payload-pair.js @@ -0,0 +1,52 @@ +// Generated by CoffeeScript 1.6.3 +var exampleToHttpPayloadPair, inheritHeaders; + +inheritHeaders = require('./inherit-headers'); + +exampleToHttpPayloadPair = function(example, inheritingHeaders) { + var request, response, result, selectedRequest, selectedResponse, text; + if (inheritingHeaders == null) { + inheritingHeaders = {}; + } + result = { + warnings: [], + errors: [], + pair: {} + }; + request = {}; + response = {}; + if (example['requests'].length > 1) { + text = "Multiple requests, using first."; + result['warnings'].push(text); + } + if (example['responses'].length > 1) { + text = "Multiple responses, using first."; + result['warnings'].push(text); + } + if (example['responses'].length === 0) { + text = "No response available. Can't create HTTP transaction."; + result['warnings'].push(text); + } else { + selectedRequest = example['requests'][0]; + selectedResponse = example['responses'][0]; + if (example['requests'].length === 0) { + selectedRequest = { + body: "", + headers: {} + }; + } + request['body'] = selectedRequest['body']; + request['headers'] = inheritHeaders(selectedRequest['headers'], inheritingHeaders); + response['body'] = selectedResponse['body']; + response['headers'] = inheritHeaders(selectedResponse['headers'], inheritingHeaders); + response['status'] = selectedResponse['name']; + if (selectedResponse['schema'] !== "") { + response['schema'] = selectedResponse['schema']; + } + result['pair']['request'] = request; + result['pair']['response'] = response; + } + return result; +}; + +module.exports = exampleToHttpPayloadPair; diff --git a/lib/execute-transaction.js b/lib/execute-transaction.js new file mode 100644 index 000000000..61fe4de24 --- /dev/null +++ b/lib/execute-transaction.js @@ -0,0 +1,173 @@ +// Generated by CoffeeScript 1.6.3 +var cli, executeTransaction, flattenHeaders, gavel, html, http, https, indent, os, packageConfig, prettify, url; + +flattenHeaders = require('./flatten-headers'); + +gavel = require('gavel'); + +http = require('http'); + +https = require('https'); + +html = require('html'); + +url = require('url'); + +os = require('os'); + +packageConfig = require('./../package.json'); + +cli = require('cli'); + +indent = ' '; + +String.prototype.trunc = function(n) { + if (this.length > n) { + return this.substr(0, n - 1) + '...'; + } else { + return this; + } +}; + +String.prototype.startsWith = function(str) { + return this.slice(0, str.length) === str; +}; + +prettify = function(transaction) { + var e, parsed, type; + type = (transaction != null ? transaction.headers['Content-Type'] : void 0) || (transaction != null ? transaction.headers['content-type'] : void 0); + switch (type) { + case 'application/json': + try { + parsed = JSON.parse(transaction.body); + } catch (_error) { + e = _error; + cli.error("Error parsing body as json: " + transaction.body); + parsed = transaction.body; + } + transaction.body = parsed; + break; + case 'text/html': + transaction.body = html.prettyPrint(transaction.body, { + indent_size: 2 + }); + } + return transaction; +}; + +executeTransaction = function(transaction, callback) { + var buffer, configuration, description, flatHeaders, handleRequest, header, options, origin, parsedUrl, req, request, response, system, value, _ref, _ref1; + configuration = transaction['configuration']; + origin = transaction['origin']; + request = transaction['request']; + response = transaction['response']; + parsedUrl = url.parse(configuration['server']); + flatHeaders = flattenHeaders(request['headers']); + if (flatHeaders['User-Agent'] === void 0) { + system = os.type() + ' ' + os.release() + '; ' + os.arch(); + flatHeaders['User-Agent'] = "Dredd/" + packageConfig['version'] + " (" + system + ")"; + } + if (((_ref = configuration.request) != null ? _ref.headers : void 0) != null) { + _ref1 = configuration['request']['headers']; + for (header in _ref1) { + value = _ref1[header]; + flatHeaders[header] = value; + } + } + options = { + host: parsedUrl['hostname'], + port: parsedUrl['port'], + path: request['uri'], + method: request['method'], + headers: flatHeaders + }; + description = origin['resourceGroupName'] + ' > ' + origin['resourceName'] + ' > ' + origin['actionName'] + ' > ' + origin['exampleName'] + ':\n' + indent + options['method'] + ' ' + options['path'] + ' ' + JSON.stringify(request['body']).trunc(20); + if (configuration.options['dry-run']) { + cli.info("Dry run, skipping..."); + return callback(); + } else { + buffer = ""; + handleRequest = function(res) { + res.on('data', function(chunk) { + return buffer = buffer + chunk; + }); + req.on('error', function(error) { + return callback(error); + }); + return res.on('end', function() { + var expected, real; + real = { + headers: res.headers, + body: buffer, + status: res.statusCode + }; + expected = { + headers: flattenHeaders(response['headers']), + body: response['body'], + bodySchema: response['schema'], + statusCode: response['status'] + }; + return gavel.isValid(real, expected, 'response', function(error, isValid) { + var test; + if (error) { + return callback(error); + } + if (isValid) { + test = { + status: "pass", + title: options['method'] + ' ' + options['path'], + message: description + }; + configuration.reporter.addTest(test, function(error) { + if (error) { + return callback(error); + } + }); + return callback(); + } else { + return gavel.validate(real, expected, 'response', function(error, result) { + var data, entity, entityResult, message, _i, _len, _ref2; + if (error) { + return callback(error); + } + message = ''; + for (entity in result) { + data = result[entity]; + _ref2 = data['results']; + for (_i = 0, _len = _ref2.length; _i < _len; _i++) { + entityResult = _ref2[_i]; + message += entity + ": " + entityResult['message'] + "\n"; + } + } + test = { + status: "fail", + title: options['method'] + ' ' + options['path'], + message: message, + actual: prettify(real), + expected: prettify(expected), + request: options + }; + configuration.reporter.addTest(test, function(error) { + if (error) { + return callback(error); + } + }); + return callback(); + }); + } + }); + }); + }; + if (configuration.server.startsWith('https')) { + req = https.request(options, handleRequest); + } else { + req = http.request(options, handleRequest); + } + if (request['body'] !== '') { + req.write(request['body']); + } + return req.end(); + } +}; + +module.exports = executeTransaction; diff --git a/lib/expand-uri-template-with-parameters.js b/lib/expand-uri-template-with-parameters.js new file mode 100644 index 000000000..d654630f7 --- /dev/null +++ b/lib/expand-uri-template-with-parameters.js @@ -0,0 +1,81 @@ +// Generated by CoffeeScript 1.6.3 +var expandUriTemplateWithParameters, ut; + +ut = require('uri-template'); + +expandUriTemplateWithParameters = function(uriTemplate, parameters) { + var ambigous, e, expression, param, parameter, parsed, result, text, toExpand, uriParameter, uriParameters, _i, _j, _k, _l, _len, _len1, _len2, _len3, _len4, _m, _ref, _ref1, _ref2; + result = { + errors: [], + warnings: [], + uri: null + }; + try { + parsed = ut.parse(uriTemplate); + } catch (_error) { + e = _error; + text = "\n Failed to parse URI template: " + uriTemplate; + text += "\n Error: " + e; + result['errors'].push(text); + return result; + } + uriParameters = []; + _ref = parsed['expressions']; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + expression = _ref[_i]; + _ref1 = expression['params']; + for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { + param = _ref1[_j]; + uriParameters.push(param['name']); + } + } + _ref2 = Object.keys(parameters); + for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { + parameter = _ref2[_k]; + if (uriParameters.indexOf(parameter) === -1) { + text = ("\nURI template: " + uriTemplate + "\nDoesn\'t contain expression for parameter") + " '" + parameter + "'"; + result['warnings'].push(text); + } + } + if (parsed['expressions'].length === 0) { + result['uri'] = uriTemplate; + } else { + ambigous = false; + for (_l = 0, _len3 = uriParameters.length; _l < _len3; _l++) { + uriParameter = uriParameters[_l]; + if (Object.keys(parameters).indexOf(uriParameter) === -1) { + ambigous = true; + text = ("\nAmbigous URI template: " + uriTemplate + " ") + "\nParameter not defined:" + "'" + uriParameter + "'"; + result['warnings'].push(text); + } + } + if (ambigous === false) { + toExpand = {}; + for (_m = 0, _len4 = uriParameters.length; _m < _len4; _m++) { + uriParameter = uriParameters[_m]; + param = parameters[uriParameter]; + if (param['required'] === true) { + if (param['example'] === void 0) { + ambigous = true; + text = ("\nAmbigous URI template: " + uriTemplate + " ") + "\nNo example value for parameter:" + "'" + uriParameter + "'"; + result['warnings'].push(text); + } else { + toExpand[uriParameter] = param['example']; + } + } else { + if (param['example'] !== void 0) { + toExpand[uriParameter] = param['example']; + } else if (param['default'] !== void 0) { + toExpand[uriParameter] = param['default']; + } + } + } + } + if (ambigous === false) { + result['uri'] = parsed.expand(toExpand); + } + } + return result; +}; + +module.exports = expandUriTemplateWithParameters; diff --git a/lib/flatten-headers.js b/lib/flatten-headers.js new file mode 100644 index 000000000..aaa591a65 --- /dev/null +++ b/lib/flatten-headers.js @@ -0,0 +1,14 @@ +// Generated by CoffeeScript 1.6.3 +var flattenHeaders; + +flattenHeaders = function(blueprintHeaders) { + var flatHeaders, name, values; + flatHeaders = {}; + for (name in blueprintHeaders) { + values = blueprintHeaders[name]; + flatHeaders[name] = values['value'].toLowerCase(); + } + return flatHeaders; +}; + +module.exports = flattenHeaders; diff --git a/lib/inherit-headers.js b/lib/inherit-headers.js new file mode 100644 index 000000000..bc2da261c --- /dev/null +++ b/lib/inherit-headers.js @@ -0,0 +1,15 @@ +// Generated by CoffeeScript 1.6.3 +var inheritHeaders; + +inheritHeaders = function(actualHeaders, inheritingHeaders) { + var name, params; + for (name in inheritingHeaders) { + params = inheritingHeaders[name]; + if (actualHeaders[name] === void 0) { + actualHeaders[name] = params; + } + } + return actualHeaders; +}; + +module.exports = inheritHeaders; diff --git a/lib/inherit-parameters.js b/lib/inherit-parameters.js new file mode 100644 index 000000000..4ad30f63f --- /dev/null +++ b/lib/inherit-parameters.js @@ -0,0 +1,15 @@ +// Generated by CoffeeScript 1.6.3 +var inheritParameters; + +inheritParameters = function(actualParameters, inheritingParameters) { + var name, params; + for (name in inheritingParameters) { + params = inheritingParameters[name]; + if (actualParameters[name] === void 0) { + actualParameters[name] = params; + } + } + return actualParameters; +}; + +module.exports = inheritParameters; diff --git a/lib/logger.js b/lib/logger.js new file mode 100644 index 000000000..a2df5161b --- /dev/null +++ b/lib/logger.js @@ -0,0 +1,41 @@ +// Generated by CoffeeScript 1.6.3 +var config, logger, winston; + +winston = require('winston'); + +config = { + levels: { + test: 0, + info: 1, + pass: 2, + fail: 3, + complete: 4, + actual: 5, + expected: 6, + diff: 7, + request: 8 + }, + colors: { + test: 'yellow', + info: 'blue', + pass: 'green', + fail: 'red', + complete: 'green', + actual: 'red', + expected: 'red', + diff: 'red', + request: 'green' + } +}; + +logger = new winston.Logger({ + transports: [ + new winston.transports.Console({ + colorize: true + }) + ], + levels: config.levels, + colors: config.colors +}); + +module.exports = logger; diff --git a/lib/reporter.js b/lib/reporter.js new file mode 100644 index 000000000..1a41136cf --- /dev/null +++ b/lib/reporter.js @@ -0,0 +1,71 @@ +// Generated by CoffeeScript 1.6.3 +var Reporter, + __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + +Reporter = (function() { + function Reporter() { + this.addReporter = __bind(this.addReporter, this); + this.createReport = __bind(this.createReport, this); + this.addTest = __bind(this.addTest, this); + this.reporters = []; + this.type = "Container"; + this.tests = []; + this.stats = { + tests: 0, + failures: 0, + passes: 0, + timestamp: (new Date).toUTCString(), + duration: 0 + }; + } + + Reporter.prototype.addTest = function(test, callback) { + var reporter, _i, _len, _ref; + _ref = this.reporters; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + reporter = _ref[_i]; + reporter.addTest(test, function(error) { + if (error) { + return callback(error); + } + }); + } + this.tests.push(test); + this.stats.tests += 1; + switch (test.status) { + case 'pass': + this.stats.passes += 1; + break; + case 'fail': + this.stats.failures += 1; + break; + default: + return callback(new Error("Error adding test: must have status of pass or fail.")); + } + return callback(); + }; + + Reporter.prototype.createReport = function(callback) { + var reporter, _i, _len, _ref; + _ref = this.reporters; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + reporter = _ref[_i]; + reporter.createReport(function(error) { + if (error) { + return callback(error); + } + }); + } + return callback(); + }; + + Reporter.prototype.addReporter = function(reporter) { + this.reporters.push(reporter); + return this; + }; + + return Reporter; + +})(); + +module.exports = Reporter; diff --git a/lib/x-unit-reporter.js b/lib/x-unit-reporter.js new file mode 100644 index 000000000..d14f93868 --- /dev/null +++ b/lib/x-unit-reporter.js @@ -0,0 +1,110 @@ +// Generated by CoffeeScript 1.6.3 +var Reporter, XUnitReporter, cli, fs, htmlencode, + __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + +fs = require('fs'); + +cli = require('cli'); + +Reporter = require('./reporter'); + +htmlencode = require('htmlencode'); + +XUnitReporter = (function(_super) { + var appendLine, cdata, doTest, toTag; + + __extends(XUnitReporter, _super); + + function XUnitReporter(path) { + this.createReport = __bind(this.createReport, this); + this.addTest = __bind(this.addTest, this); + XUnitReporter.__super__.constructor.call(this); + this.type = "xUnit"; + if (path == null) { + this.path = process.cwd() + "/report.xml"; + } + if (fs.existsSync(this.path)) { + fs.unlinkSync(this.path); + } + } + + XUnitReporter.prototype.addTest = function(test, callback) { + XUnitReporter.__super__.addTest.call(this, test, function(error) { + if (error) { + return callback(error); + } + }); + cli.debug("Adding test to junit reporter: " + JSON.stringify(test)); + return callback(); + }; + + XUnitReporter.prototype.createReport = function(callback) { + var test, _i, _len, _ref; + XUnitReporter.__super__.createReport.call(this, function(error) { + if (error) { + return callback(error); + } + }); + cli.debug("Writing junit tests to file: " + this.path); + appendLine(this.path, toTag('testsuite', { + name: 'Dredd Tests', + tests: this.stats.tests, + failures: this.stats.failures, + errors: this.stats.failures, + skip: this.stats.tests - this.stats.failures - this.stats.passes, + timestamp: (new Date).toUTCString(), + time: this.stats.duration / 1000 + }, false)); + _ref = this.tests; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + test = _ref[_i]; + doTest(this.path, test); + } + appendLine(this.path, ''); + return callback(); + }; + + doTest = function(path, test) { + var attrs, diff; + attrs = { + name: htmlencode.htmlEncode(test.title), + time: 0 + }; + if ('fail' === test.status) { + diff = "Message: \n" + test.message + "\nExpected: \n" + (JSON.stringify(test.expected, null, 4)) + "\nActual:\n" + (JSON.stringify(test.actual, null, 4)); + return appendLine(path, toTag('testcase', attrs, false, toTag('failure', null, false, cdata(diff)))); + } else { + return appendLine(path, toTag('testcase', attrs, true)); + } + }; + + cdata = function(str) { + return ''; + }; + + appendLine = function(path, line) { + return fs.appendFileSync(path, line + "\n"); + }; + + toTag = function(name, attrs, close, content) { + var end, key, pairs, tag; + end = (close ? "/>" : ">"); + pairs = []; + tag = void 0; + for (key in attrs) { + pairs.push(key + "=\"" + attrs[key] + "\""); + } + tag = "<" + name + (pairs.length ? " " + pairs.join(" ") : "") + end; + if (content) { + tag += content + "