From 196d98d64f396e99449113eade3271aecdaa270c Mon Sep 17 00:00:00 2001 From: Nick Schonning <nschonni@gmail.com> Date: Wed, 8 Oct 2014 00:37:48 -0400 Subject: [PATCH] Testing: Update the YUI Test harness for Grunt - Fails the build on test failure - Splits out our custom Grunt tasks - Update the relative require paths after moving the tasks from the Gruntfile --- Gruntfile.js | 241 +--------------------------------------- tasks/changelog.js | 72 ++++++++++++ tasks/test_rhino.js | 23 ++++ tasks/yuitest.js | 105 +++++++++++++++++ tests/cli/cli-common.js | 6 +- 5 files changed, 208 insertions(+), 239 deletions(-) create mode 100644 tasks/changelog.js create mode 100644 tasks/test_rhino.js create mode 100644 tasks/yuitest.js diff --git a/Gruntfile.js b/Gruntfile.js index 75078270..d7058ec5 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,4 +1,4 @@ -/* jshint camelcase:false, evil:true, node:true */ +/* jshint camelcase:false, node:true */ "use strict"; @@ -166,7 +166,7 @@ module.exports = function(grunt) { jshintrc: ".jshintrc" }, gruntfile: { - src: "Gruntfile.js" + src: ["Gruntfile.js","tasks/*.js"] }, demo: { src: "demos/*.js" @@ -217,6 +217,9 @@ module.exports = function(grunt) { grunt.loadNpmTasks("grunt-contrib-watch"); grunt.loadNpmTasks("grunt-include-replace"); + // Load custom tasks + grunt.loadTasks("tasks"); + // Default task. grunt.registerTask("default", ["test"]); @@ -228,238 +231,4 @@ module.exports = function(grunt) { grunt.registerTask("rhino", ["clean:build", "jshint", "concat", "test_rhino"]); grunt.registerTask("release", ["test", "clean:release", "copy:release", "includereplace:release", "changelog"]); - - //Run the YUITest suite - grunt.registerMultiTask("yuitest", "Run the YUITests for the project", function() { - - var YUITest = require("yuitest"); - var CSSLint = require("./build/csslint-node").CSSLint; // jshint ignore:line - var files = this.filesSrc; - var TestRunner = YUITest.TestRunner; - var done = this.async(); - var errors = [], - failures = [], - stack = []; - - //Eval each file so the tests are brought into this scope where CSSLint and YUITest are loaded already - files.forEach(function(filepath) { - eval(grunt.file.read(filepath)); - }); - - // From YUITest Node CLI - function filterStackTrace(stackTrace){ - if (stackTrace){ - var lines = stackTrace.split("\n"), - result = [], - i, len; - - //skip first line, it's the error - for (i=1, len=lines.length; i < len; i++){ - if (lines[i].indexOf("yuitest-node") > -1){ - break; - } else { - result.push(lines[i]); - } - } - - return result.join("\n"); - } else { - return "Unavailable."; - } - } - - // From YUITest Node CLI with minor colourization changes - function handleEvent(event){ - - var message = "", - results = event.results, - i, len; - - switch(event.type){ - case TestRunner.BEGIN_EVENT: - message = "YUITest for Node.js\n"; - - if (TestRunner._groups){ - message += "Filtering on groups '" + TestRunner._groups.slice(1,-1) + "'\n"; - } - break; - - case TestRunner.COMPLETE_EVENT: - message = "\nTotal tests: " + results.total + ", " + - ("Failures: " + results.failed).red + ", " + - ("Skipped: " + results.ignored).yellow + - ", Time: " + (results.duration/1000) + " seconds\n"; - - if (failures.length){ - message += "\nTests failed:\n".red; - - for (i=0,len=failures.length; i < len; i++){ - message += "\n" + (i+1) + ") " + failures[i].name + " : " + failures[i].error.getMessage() + "\n"; - message += "Stack trace:\n" + filterStackTrace(failures[i].error.stack) + "\n"; - } - - message += "\n"; - } - - if (errors.length){ - message += "\nErrors:\n".red; - - for (i=0,len=errors.length; i < len; i++){ - message += "\n" + (i+1) + ") " + errors[i].name + " : " + errors[i].error.message + "\n"; - message += "Stack trace:\n" + filterStackTrace(errors[i].error.stack) + "\n"; - } - - message += "\n"; - } - - message += "\n\n"; - //Tell grunt we're done the async operation - done(); - break; - - case TestRunner.TEST_FAIL_EVENT: - message = "F".red; - failures.push({ - name: stack.concat([event.testName]).join(" > "), - error: event.error - }); - - break; - - case TestRunner.ERROR_EVENT: - errors.push({ - name: stack.concat([event.methodName]).join(" > "), - error: event.error - }); - - break; - - case TestRunner.TEST_IGNORE_EVENT: - message = "S".yellow; - break; - - case TestRunner.TEST_PASS_EVENT: - message = ".".green; - break; - - case TestRunner.TEST_SUITE_BEGIN_EVENT: - stack.push(event.testSuite.name); - break; - - case TestRunner.TEST_CASE_COMPLETE_EVENT: - case TestRunner.TEST_SUITE_COMPLETE_EVENT: - stack.pop(); - break; - - case TestRunner.TEST_CASE_BEGIN_EVENT: - stack.push(event.testCase.name); - break; - - //no default - } - - grunt.log.write(message); - } - //Add event listeners - TestRunner.subscribe(TestRunner.BEGIN_EVENT, handleEvent); - TestRunner.subscribe(TestRunner.TEST_FAIL_EVENT, handleEvent); - TestRunner.subscribe(TestRunner.TEST_PASS_EVENT, handleEvent); - TestRunner.subscribe(TestRunner.ERROR_EVENT, handleEvent); - TestRunner.subscribe(TestRunner.TEST_IGNORE_EVENT, handleEvent); - TestRunner.subscribe(TestRunner.TEST_CASE_BEGIN_EVENT, handleEvent); - TestRunner.subscribe(TestRunner.TEST_CASE_COMPLETE_EVENT, handleEvent); - TestRunner.subscribe(TestRunner.TEST_SUITE_BEGIN_EVENT, handleEvent); - TestRunner.subscribe(TestRunner.TEST_SUITE_COMPLETE_EVENT, handleEvent); - TestRunner.subscribe(TestRunner.COMPLETE_EVENT, handleEvent); - TestRunner.run(); - }); - - grunt.registerMultiTask("changelog", "Write the changelog file", function() { - var done = this.async(); - var lastTag; - var files = this.filesSrc; - - - grunt.util.spawn({ - cmd: "git", - args: ["tag"] - }, function(error, result) { - //Find the latest git tag - var tags = result.stdout.split("\n"), - semver = tags[0].replace("v","").split("."), - major = parseInt(semver[0], 10), - minor = parseInt(semver[1], 10), - patch = parseInt(semver[2], 10); - - //A simple array sort can't be used because of the comparison of - //the strings "0.9.9" > "0.9.10" - for (var i = 1, len = tags.length; i < len; i++) { - semver = tags[i].replace("v", "").split("."); - - var currentMajor = parseInt(semver[0], 10); - if (currentMajor < major) { - continue; - } else if (currentMajor > major) { - major = currentMajor; - } - - var currentMinor = parseInt(semver[1], 10); - if (currentMinor < minor) { - continue; - } else if (currentMinor > minor) { - minor = currentMinor; - } - - var currentPatch = parseInt(semver[2], 10); - if (currentPatch < patch) { - continue; - } else if (currentPatch > patch) { - patch = currentPatch; - } - } - - lastTag = "v" + major + "." + minor + "." + patch; - - grunt.verbose.write("Last tag: " + lastTag).writeln(); - - // - grunt.util.spawn({ - cmd: "git", - args: ["log", "--pretty=format:'* %s (%an)'", lastTag + "..HEAD"] - }, function(error, result) { - var prettyPrint = result.stdout.split("'\n'").join("\n").replace(/\"$/, "").replace(/^\"/, ""); - - grunt.verbose.writeln().write(prettyPrint).writeln(); - - var template = "<%= grunt.template.today('mmmm d, yyyy') %> - v<%= pkg.version %>\n\n" + - prettyPrint + "\n\n" + - grunt.file.read(files[0]); - - grunt.file.write(files[0], grunt.template.process(template)); - - done(); - }); - }); - - }); - - //Run test suite through rhino - grunt.registerMultiTask("test_rhino", "Run the test suite through rhino", function() { - var done = this.async(); - var files = this.filesSrc; - var progress = files.length; - - files.forEach(function(filepath) { - grunt.util.spawn({ - cmd: "java", - args: ["-jar", "lib/js.jar", "lib/yuitest-rhino-cli.js", "build/csslint.js", filepath], - opts: {stdio: "inherit"} - }, function() { - progress--; - if (progress === 0) { - done(); - } - }); - }); - }); }; diff --git a/tasks/changelog.js b/tasks/changelog.js new file mode 100644 index 00000000..a7a0c875 --- /dev/null +++ b/tasks/changelog.js @@ -0,0 +1,72 @@ +/* jshint node:true */ +module.exports = function( grunt ) { + "use strict"; + grunt.registerMultiTask("changelog", "Write the changelog file", function() { + var done = this.async(); + var lastTag; + var files = this.filesSrc; + + + grunt.util.spawn({ + cmd: "git", + args: ["tag"] + }, function(error, result) { + //Find the latest git tag + var tags = result.stdout.split("\n"), + semver = tags[0].replace("v","").split("."), + major = parseInt(semver[0], 10), + minor = parseInt(semver[1], 10), + patch = parseInt(semver[2], 10); + + //A simple array sort can't be used because of the comparison of + //the strings "0.9.9" > "0.9.10" + for (var i = 1, len = tags.length; i < len; i++) { + semver = tags[i].replace("v", "").split("."); + + var currentMajor = parseInt(semver[0], 10); + if (currentMajor < major) { + continue; + } else if (currentMajor > major) { + major = currentMajor; + } + + var currentMinor = parseInt(semver[1], 10); + if (currentMinor < minor) { + continue; + } else if (currentMinor > minor) { + minor = currentMinor; + } + + var currentPatch = parseInt(semver[2], 10); + if (currentPatch < patch) { + continue; + } else if (currentPatch > patch) { + patch = currentPatch; + } + } + + lastTag = "v" + major + "." + minor + "." + patch; + + grunt.verbose.write("Last tag: " + lastTag).writeln(); + + // + grunt.util.spawn({ + cmd: "git", + args: ["log", "--pretty=format:'* %s (%an)'", lastTag + "..HEAD"] + }, function(error, result) { + var prettyPrint = result.stdout.split("'\n'").join("\n").replace(/\"$/, "").replace(/^\"/, ""); + + grunt.verbose.writeln().write(prettyPrint).writeln(); + + var template = "<%= grunt.template.today('mmmm d, yyyy') %> - v<%= pkg.version %>\n\n" + + prettyPrint + "\n\n" + + grunt.file.read(files[0]); + + grunt.file.write(files[0], grunt.template.process(template)); + + done(); + }); + }); + + }); +}; diff --git a/tasks/test_rhino.js b/tasks/test_rhino.js new file mode 100644 index 00000000..223e8910 --- /dev/null +++ b/tasks/test_rhino.js @@ -0,0 +1,23 @@ +/* jshint node:true */ +module.exports = function( grunt ) { + "use strict"; + // Run test suite through rhino + grunt.registerMultiTask("test_rhino", "Run the test suite through rhino", function() { + var done = this.async(); + var files = this.filesSrc; + var progress = files.length; + + files.forEach(function(filepath) { + grunt.util.spawn({ + cmd: "java", + args: ["-jar", "lib/js.jar", "lib/yuitest-rhino-cli.js", "build/csslint.js", filepath], + opts: {stdio: "inherit"} + }, function() { + progress--; + if (progress === 0) { + done(); + } + }); + }); + }); +}; diff --git a/tasks/yuitest.js b/tasks/yuitest.js new file mode 100644 index 00000000..c3564496 --- /dev/null +++ b/tasks/yuitest.js @@ -0,0 +1,105 @@ +/* jshint evil:true, node:true */ +module.exports = function( grunt ) { + "use strict"; + grunt.registerMultiTask("yuitest", "Run the YUITests for the project", function() { + + var YUITest = require("yuitest"); + var CSSLint = require("../build/csslint-node").CSSLint; // jshint ignore:line + var files = this.filesSrc; + var TestRunner = YUITest.TestRunner; + var done = this.async(); + var failures = [], + stack = []; + + // Eval each file so the tests are brought into this scope where CSSLint and YUITest are loaded already + files.forEach(function(filepath) { + eval(grunt.file.read(filepath)); + }); + + // From YUITest Node CLI with minor colourization changes + function handleEvent(event){ + + var message = "", + results = event.results, + i, len; + + switch(event.type){ + case TestRunner.BEGIN_EVENT: + grunt.verbose.subhead("YUITest for Node.js"); + + if (TestRunner._groups){ + grunt.verbose.writeln("Filtering on groups '" + TestRunner._groups.slice(1,-1) + "'"); + } + break; + + case TestRunner.COMPLETE_EVENT: + grunt.log.writeln("Total tests: " + results.total + ", " + + ("Failures: " + results.failed).red + ", " + + ("Skipped: " + results.ignored).yellow + + ", Time: " + (results.duration/1000) + " seconds\n"); + + if (failures.length){ + grunt.log.writeln("Tests failed:"); + + for (i=0,len=failures.length; i < len; i++){ + grunt.fail.warn(failures[i].name + "\n" + failures[i].error); + } + } + + // Tell grunt we're done the async operation + done(); + break; + + case TestRunner.TEST_FAIL_EVENT: + message = "F".red; + failures.push({ + name: stack.concat([event.testName]).join(" > "), + error: event.error + }); + + break; + + case TestRunner.ERROR_EVENT: + grunt.fail.fatal(event.error, stack); + break; + + case TestRunner.TEST_IGNORE_EVENT: + message = "S".yellow; + break; + + case TestRunner.TEST_PASS_EVENT: + message = ".".green; + break; + + case TestRunner.TEST_SUITE_BEGIN_EVENT: + stack.push(event.testSuite.name); + break; + + case TestRunner.TEST_CASE_COMPLETE_EVENT: + case TestRunner.TEST_SUITE_COMPLETE_EVENT: + stack.pop(); + break; + + case TestRunner.TEST_CASE_BEGIN_EVENT: + stack.push(event.testCase.name); + break; + + //no default + } + + grunt.log.write(message); + } + //Add event listeners + TestRunner.subscribe(TestRunner.BEGIN_EVENT, handleEvent); + TestRunner.subscribe(TestRunner.TEST_FAIL_EVENT, handleEvent); + TestRunner.subscribe(TestRunner.TEST_PASS_EVENT, handleEvent); + TestRunner.subscribe(TestRunner.ERROR_EVENT, handleEvent); + TestRunner.subscribe(TestRunner.TEST_IGNORE_EVENT, handleEvent); + TestRunner.subscribe(TestRunner.TEST_CASE_BEGIN_EVENT, handleEvent); + TestRunner.subscribe(TestRunner.TEST_CASE_COMPLETE_EVENT, handleEvent); + TestRunner.subscribe(TestRunner.TEST_SUITE_BEGIN_EVENT, handleEvent); + TestRunner.subscribe(TestRunner.TEST_SUITE_COMPLETE_EVENT, handleEvent); + TestRunner.subscribe(TestRunner.COMPLETE_EVENT, handleEvent); + TestRunner.run(); + }); +}; diff --git a/tests/cli/cli-common.js b/tests/cli/cli-common.js index 012196bd..5871fa5d 100644 --- a/tests/cli/cli-common.js +++ b/tests/cli/cli-common.js @@ -16,15 +16,15 @@ function include(path, sandbox) { var Assert = YUITest.Assert, suite = new YUITest.TestSuite("General Tests for CLI"), - apiStub = require("./tests/cli/assets/apiStub.js"), - data = require("./tests/cli/assets/data.js"), + apiStub = require("../tests/cli/assets/apiStub.js"), + data = require("../tests/cli/assets/data.js"), suites = data.suites, suiteix, sandbox = { CSSLint: CSSLint }; - include(__dirname + "/src/cli/common.js", sandbox); /* expose sandbox.cli */ + include("./src/cli/common.js", sandbox); /* expose sandbox.cli */ for (suiteix in suites) { if (suites.hasOwnProperty(suiteix)) {