├── .gitignore ├── .npmignore ├── lib ├── extern │ ├── optparse │ │ ├── TODO │ │ ├── seed.yml │ │ ├── package.json │ │ ├── examples │ │ │ ├── browser-test.html │ │ │ └── nodejs-test.js │ │ └── README.md │ └── long-stack-traces │ │ ├── index.js │ │ ├── package.json │ │ ├── examples.js │ │ ├── examples.html │ │ ├── README.md │ │ └── lib │ │ └── long-stack-traces.js ├── reporters │ ├── coverage │ │ ├── base.js │ │ ├── json.js │ │ ├── cli.js │ │ └── html.js │ ├── scope-leaks │ │ ├── base.js │ │ └── cli.js │ ├── test │ │ ├── base.js │ │ ├── tap.js │ │ └── cli.js │ └── index.js ├── gen_makefile.js ├── scopeleaks.js ├── errors.js ├── process_runner │ └── run.js ├── run_test_file.js ├── parser.js ├── bdd.js ├── constants.js ├── coverage.js ├── util.js └── assert.js ├── example ├── test-no-test-functions.js ├── empty-global-setup-teardown.js ├── test-long-running-1.js ├── test-long-running-2.js ├── test-throw-string.js ├── custom-assert-functions.js ├── test-custom-assert-functions.js ├── timeout-throws.js ├── malformed-javascript-source.js ├── dependencies.json ├── test-succeeded-tests-are-reported-on-timeout.js ├── test-stdout-and-stderr-is-captured-on-timeout.js ├── test-coverage.js ├── init-timeout.js ├── test-chdir.js ├── local-setup.js ├── test-timeout-blocking.js ├── global-setup.js ├── local-teardown.js ├── global-teardown.js ├── global-setup-with-failed-assertion.js ├── test-setup-timeout.js ├── test-timeout-after-finish.js ├── test-success-with-coverage.js ├── test-teardown-timeout.js ├── global-setup-with-exception.js ├── test-failure.js ├── global-teardown-with-exception.js ├── init-test.js ├── init.js ├── test-skipped.js ├── test-init-function.js ├── test-setup-fail.js ├── test-scope-leaks.js ├── test-print-stdout-stderr-timeout.js ├── test-initialize-and-finalize-function.js ├── test-success.js ├── test-timeout.js ├── test-setup-and-teardown.js ├── some-file.js ├── test-bdd-failures.js ├── test-getFilePathAndPattern.js ├── test-leaks.js ├── test-custom-assert-methods.js ├── test-uncaught.js ├── test-bdd.js └── test-spyon.js ├── assets ├── keybd_open.png ├── keybd_closed.png ├── Makefile.magic ├── jquery.isonscreen.js ├── jquery.hotkeys.js ├── whiskey_source.magic ├── whiskey.magic └── style.css ├── Makefile ├── .travis.yml ├── jshint.json ├── scripts └── lint.sh ├── index.js ├── bin ├── whiskey-process-runner └── whiskey ├── package.json ├── PROCESS_RUNNER.md ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | lib-cov/ 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .lvimrc 2 | .gitignore 3 | node_modules 4 | lib-cov 5 | -------------------------------------------------------------------------------- /lib/extern/optparse/TODO: -------------------------------------------------------------------------------- 1 | - Support for Argument lists (for switches) -------------------------------------------------------------------------------- /example/test-no-test-functions.js: -------------------------------------------------------------------------------- 1 | exports['some_function'] = function() {}; 2 | -------------------------------------------------------------------------------- /example/empty-global-setup-teardown.js: -------------------------------------------------------------------------------- 1 | // Tests depend on this file remaining empty. 2 | -------------------------------------------------------------------------------- /lib/extern/long-stack-traces/index.js: -------------------------------------------------------------------------------- 1 | exports = require('./lib/long-stack-traces') 2 | -------------------------------------------------------------------------------- /assets/keybd_open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudkick/whiskey/HEAD/assets/keybd_open.png -------------------------------------------------------------------------------- /assets/keybd_closed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudkick/whiskey/HEAD/assets/keybd_closed.png -------------------------------------------------------------------------------- /lib/extern/optparse/seed.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: optparse 3 | description: Command-line option parser 4 | tags: option parser command-line cli terminal 5 | version: 1.0.1 6 | -------------------------------------------------------------------------------- /example/test-long-running-1.js: -------------------------------------------------------------------------------- 1 | exports.test_long_running_1 = function(test, assert) { 2 | setTimeout(function() { 3 | assert.equal(1,1); 4 | test.finish(); 5 | }, 5000); 6 | } 7 | -------------------------------------------------------------------------------- /example/test-long-running-2.js: -------------------------------------------------------------------------------- 1 | exports.test_long_running_2 = function(test, assert) { 2 | setTimeout(function() { 3 | assert.equal(2,2); 4 | test.finish(); 5 | }, 5000); 6 | } 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CWD=`pwd` 2 | 3 | test: 4 | ${CWD}/test/run.sh 5 | 6 | example: 7 | ${CWD}/bin/whiskey --tests "${CWD}/example/test-success.js ${CWD}/example/test-failure.js" 8 | 9 | .PHONY: test example 10 | -------------------------------------------------------------------------------- /example/test-throw-string.js: -------------------------------------------------------------------------------- 1 | exports['test_throw_string1'] = function(test, assert) { 2 | throw 'fooo'; 3 | }; 4 | 5 | exports['test_throw_string2'] = function(test, assert) { 6 | throw new String('string2'); 7 | }; 8 | -------------------------------------------------------------------------------- /example/custom-assert-functions.js: -------------------------------------------------------------------------------- 1 | function assertThrow() { 2 | throw new Error('Assert thrown'); 3 | } 4 | 5 | function assertNoop() { 6 | } 7 | 8 | exports.assertThrow = assertThrow; 9 | exports.assertNoop = assertNoop; 10 | -------------------------------------------------------------------------------- /example/test-custom-assert-functions.js: -------------------------------------------------------------------------------- 1 | exports['test_assertThrow'] = function(test, assert) { 2 | assert.assertThrow(); 3 | }; 4 | 5 | exports['test_assertNoop'] = function(test, assert) { 6 | assert.assertNoop(); 7 | test.finish(); 8 | }; 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "0.8" 5 | - "0.10" 6 | - "0.11" 7 | 8 | before_install: 9 | - npm conf set strict-ssl false 10 | 11 | script: 12 | - npm run-script lint 13 | - npm run-script test 14 | 15 | notifications: 16 | email: 17 | - tomaz+travisci@tomaz.me 18 | -------------------------------------------------------------------------------- /example/timeout-throws.js: -------------------------------------------------------------------------------- 1 | function initSecondTimeout(tag) { 2 | setTimeout(function secondTimeout() { 3 | throw new Error(tag); 4 | }, 100); 5 | } 6 | 7 | function onload() { 8 | setTimeout(function firstTimeout() { 9 | initSecondTimeout("timeout"); 10 | }, 100); 11 | } 12 | 13 | onload(); 14 | -------------------------------------------------------------------------------- /lib/extern/long-stack-traces/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "long-stack-traces", 3 | "description": "Long stacktraces for V8 implemented in user-land JavaScript.", 4 | "version": "0.1.1", 5 | "author": "Tom Robinson (http://tlrobinson.net/)", 6 | "main" : "./lib/long-stack-traces", 7 | "directories": { 8 | "lib": "./lib" 9 | }, 10 | "engines": { 11 | "node": "*" 12 | } 13 | } -------------------------------------------------------------------------------- /lib/extern/optparse/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "optparse", 3 | "author": "Johan Dahlberg", 4 | "description": "Command-line option parser", 5 | "keywords": ["option", "parser", "command-line", "cli", "terminal"], 6 | "version": "1.0.4", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/jfd/optparse-js.git" 10 | }, 11 | "main": "./lib/optparse" 12 | } 13 | -------------------------------------------------------------------------------- /example/malformed-javascript-source.js: -------------------------------------------------------------------------------- 1 | // Tests depends on this file containing illegally formed Javascript. 2 | 3 | variable exports = { 4 | globalSetUp: function () { 5 | console.error("We're fanatical about poorly formed Javascript."); 6 | } 7 | 8 | globalTearDown: function () [ 9 | console.error("If this compiles, I'll never use Javascript again."); 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /assets/Makefile.magic: -------------------------------------------------------------------------------- 1 | TEST_FILES={{ test_files }} 2 | 3 | test: 4 | whiskey --tests "${TEST_FILES}" 5 | 6 | test-fast: 7 | whiskey --tests "${TEST_FILES}" --failfast 8 | 9 | tap: 10 | whiskey --tests "${TEST_FILES}" --test-reporter tap 11 | 12 | coverage: 13 | whiskey --tests "${TEST_FILES}" --coverage --coverage-reporter html \ 14 | --coverage-dir coverage_html 15 | 16 | cov: 17 | make coverage 18 | 19 | leaks: 20 | whiskey --tests "${TEST_FILES}" --scope-leaks 21 | 22 | .PHONY: test tap coverage cov leaks 23 | -------------------------------------------------------------------------------- /jshint.json: -------------------------------------------------------------------------------- 1 | { 2 | "maxerr" : 100, 3 | "node" : true, 4 | "curly" : true, 5 | "eqeqeq" : true, 6 | "latedef" : false, 7 | "undef" : true, 8 | "newcap" : true, 9 | "nonew" : true, 10 | "onevar" : false, 11 | "trailing" : true, 12 | "white" : false, 13 | "sub" : true, 14 | "evil" : true, 15 | "onecase" : true, 16 | "strict" : false, 17 | "loopfunc" : true, 18 | "globals": {"__coverage__": false} 19 | } 20 | -------------------------------------------------------------------------------- /example/dependencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "cassandra": { 3 | "cmd": ["/usr/local/bin/cassandra/", "-f"], 4 | "cwd": ["__dirname", ".."], 5 | "log_file": "cassandra.log", 6 | "wait_for": "socket", 7 | "wait_for_options": { 8 | "host": "127.0.0.1", 9 | "port": "1234" 10 | }, 11 | "timeout": 6000, 12 | "depends": [] 13 | }, 14 | 15 | "api_server": { 16 | "cmd": ["bin/api-server"], 17 | "depends": ["cassandra"] 18 | }, 19 | 20 | "celery": { 21 | "cmd": ["celeryd", "-w", "5"], 22 | "wait_for": "stdout", 23 | "wait_for_options": { 24 | "string": "Celery has been started..." 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /example/test-succeeded-tests-are-reported-on-timeout.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var assert = require('../lib/assert').getAssertModule(); 4 | var exec = require('child_process').exec; 5 | 6 | var sprintf = require('sprintf').sprintf; 7 | 8 | var cwd = process.cwd(); 9 | 10 | exec(sprintf('%s/bin/whiskey --tests %s/example/test-timeout.js --timeout 1000', cwd, cwd), 11 | function(err, stdout, stderr) { 12 | try { 13 | assert.match(stdout, /test_success_1/); 14 | assert.match(stdout, /test_success_2/); 15 | assert.match(stdout, /test_success_3/); 16 | assert.match(stdout, /timeout/i); 17 | } 18 | catch (err2) { 19 | process.exit(4); 20 | } 21 | 22 | process.exit(0); 23 | }); 24 | 25 | -------------------------------------------------------------------------------- /example/test-stdout-and-stderr-is-captured-on-timeout.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var assert = require('../lib/assert').getAssertModule(); 4 | var exec = require('child_process').exec; 5 | 6 | var sprintf = require('sprintf').sprintf; 7 | 8 | var cwd = process.cwd(); 9 | 10 | exec(sprintf('%s/bin/whiskey --tests %s/example/test-print-stdout-stderr-timeout.js --timeout 1000', cwd, cwd), 11 | function(err, stdout, stderr) { 12 | try { 13 | assert.match(stdout, /this is stdout 1/); 14 | assert.match(stdout, /this is stdout 2/); 15 | assert.match(stdout, /this is stderr 1/); 16 | assert.match(stdout, /this is stderr 2/); 17 | } 18 | catch (err2) { 19 | process.exit(5); 20 | } 21 | 22 | process.exit(0); 23 | }); 24 | -------------------------------------------------------------------------------- /scripts/lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2013 Tomaz Muraus 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | ./node_modules/.bin/jshint $(find ./lib -type f -name "*.js" | grep -v lib/extern) --config jshint.json 19 | -------------------------------------------------------------------------------- /lib/extern/long-stack-traces/examples.js: -------------------------------------------------------------------------------- 1 | require("./index"); 2 | 3 | var fs = require("fs"); 4 | 5 | function initSecondTimeout(tag) { 6 | setTimeout(function secondTimeout() { 7 | throw new Error(tag); 8 | }, 1000); 9 | } 10 | 11 | function onload() { 12 | // function f() { 13 | // throw new Error('foo'); 14 | // } 15 | // setTimeout(f, Math.random()*1000); 16 | // setTimeout(f, Math.random()*1000); 17 | 18 | setTimeout(function firstTimeout() { 19 | initSecondTimeout("timeout"); 20 | }, 1000); 21 | 22 | fs.readFile('README.md', 'utf8', function (err, data) { 23 | if (err) throw err; 24 | initSecondTimeout("readFile"); 25 | }); 26 | } 27 | 28 | onload(); 29 | 30 | process.on('uncaughtException', function(e) { console.log('wa');console.log(e.stack)}) 31 | -------------------------------------------------------------------------------- /example/test-coverage.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var assert = require('../lib/assert').getAssertModule(); 4 | var exec = require('child_process').exec; 5 | 6 | var sprintf = require('sprintf').sprintf; 7 | 8 | var cwd = process.cwd(); 9 | 10 | exec(sprintf('%s/bin/whiskey --tests %s/example/test-success-with-coverage.js --timeout 6000 --coverage --coverage-reporter cli', cwd, cwd), 11 | function(err, stdout, stderr) { 12 | try { 13 | assert.match(stdout, /test coverage/i); 14 | assert.match(stdout, /coverage\.js\s+\|\s+\d+/); 15 | assert.match(stdout, /loc/i); 16 | assert.match(stdout, /sloc/i); 17 | assert.match(stdout, /missed/i); 18 | } 19 | catch (err2) { 20 | process.exit(5); 21 | } 22 | 23 | if (err && err.code !== 0) { 24 | process.exit(5); 25 | } 26 | 27 | process.exit(0); 28 | }); 29 | -------------------------------------------------------------------------------- /example/init-timeout.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | exports['initialize'] = function(callback) { 19 | }; 20 | -------------------------------------------------------------------------------- /example/test-chdir.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | exports['test_chdir'] = function(test, assert) { 20 | assert.ok(process.cwd().indexOf('example') !== -1); 21 | test.finish(); 22 | }; 23 | -------------------------------------------------------------------------------- /example/local-setup.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Rackspace, Inc ('Rackspace') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Rackspace licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | exports.localSetUp = function(test, assert) { 20 | console.error("globalSetUp was here"); 21 | assert.equal(1, 1); 22 | test.finish(); 23 | }; 24 | 25 | -------------------------------------------------------------------------------- /example/test-timeout-blocking.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | exports['test_test_timeout_non_blocking'] = function(test, assert) { 19 | var i = 0; 20 | while (i < 1000000); 21 | 22 | assert.ok(true); 23 | }; 24 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | exports.bdd = require('./lib/bdd.js'); 19 | exports.run = require('./lib/run.js').run; 20 | exports.installCoverageHandler = require('./lib/coverage').installCoverageHandler; 21 | -------------------------------------------------------------------------------- /example/global-setup.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Rackspace, Inc ('Rackspace') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Rackspace licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | exports.globalSetUp = function(test, assert) { 20 | console.error("globalSetUp was here"); 21 | assert.equal(1, 1); 22 | test.finish(); 23 | }; 24 | 25 | -------------------------------------------------------------------------------- /example/local-teardown.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Rackspace, Inc ('Rackspace') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Rackspace licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | exports.localTearDown = function(test, assert) { 20 | console.error("globalTearDown was here"); 21 | assert.equal(1, 1); 22 | test.finish(); 23 | }; 24 | 25 | -------------------------------------------------------------------------------- /example/global-teardown.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Rackspace, Inc ('Rackspace') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Rackspace licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | exports.globalTearDown = function(test, assert) { 20 | console.error("globalTearDown was here"); 21 | assert.equal(1, 1); 22 | test.finish(); 23 | }; 24 | 25 | -------------------------------------------------------------------------------- /example/global-setup-with-failed-assertion.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Rackspace, Inc ('Rackspace') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Rackspace licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | exports.globalSetUp = function(test, assert) { 20 | console.error("globalSetUp was here"); 21 | assert.equal(1,2); 22 | test.finish(); 23 | }; 24 | 25 | -------------------------------------------------------------------------------- /example/test-setup-timeout.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | exports['setUp'] = function(test, assert) { 19 | }; 20 | 21 | exports['test_one_equals_two'] = function(test, assert) { 22 | assert.equal(1, 2); 23 | test.finish(); 24 | }; 25 | -------------------------------------------------------------------------------- /example/test-timeout-after-finish.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | exports['test_timeout_after_finish'] = function(test, assert) { 19 | test.finish(); 20 | setTimeout(function() { 21 | assert.ok(false); 22 | }, 50000); 23 | }; 24 | -------------------------------------------------------------------------------- /example/test-success-with-coverage.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | var coverage = require('../lib/coverage'); 19 | 20 | exports['test_coverage_file_coverage'] = function(test, assert) { 21 | assert.ok(true); 22 | test.finish(); 23 | }; 24 | -------------------------------------------------------------------------------- /example/test-teardown-timeout.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | exports['test_one_equals_two'] = function(test, assert) { 19 | assert.equal(1, 2); 20 | test.finish(); 21 | }; 22 | 23 | exports['tearDown'] = function(test, assert) { 24 | }; 25 | -------------------------------------------------------------------------------- /example/global-setup-with-exception.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Rackspace, Inc ('Rackspace') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Rackspace licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | exports.globalSetUp = function(test, assert) { 20 | console.error("globalSetUp was here"); 21 | throw new Error('Exceptional exceptions except for the exceptions to the exceptions.'); 22 | test.finish(); 23 | }; 24 | 25 | -------------------------------------------------------------------------------- /example/test-failure.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | exports['test_false_is_not_equal_true'] = function(test, assert) { 19 | assert.ok(false); 20 | test.finish(); 21 | }; 22 | 23 | exports['test_one_is_not_equal_two'] = function(test, assert) { 24 | assert.equal(1, 2); 25 | test.finish(); 26 | }; 27 | -------------------------------------------------------------------------------- /example/global-teardown-with-exception.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Rackspace, Inc ('Rackspace') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Rackspace licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | exports.globalTearDown = function(test, assert) { 20 | console.error("globalTearDown was here"); 21 | throw new Error("All exceptions are exceptions to the norm, except the exceptions thereto."); 22 | test.finish(); 23 | }; 24 | 25 | -------------------------------------------------------------------------------- /example/init-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | var path = require('path'); 19 | var fs = require('fs'); 20 | 21 | var dirPath = path.join(process.cwd(), 'example/test-123456'); 22 | 23 | exports['initialize'] = function(callback) { 24 | fs.rmdir(dirPath, function(err) { 25 | callback(); 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /example/init.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | var path = require('path'); 19 | var fs = require('fs'); 20 | 21 | var dirPath = path.join(process.cwd(), 'example/test-123456'); 22 | 23 | exports['initialize'] = function(callback) { 24 | fs.mkdir(dirPath, 0655, function(err) { 25 | callback(); 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /example/test-skipped.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | exports['test_skip1'] = function(test, assert) { 19 | test.skip("some reason blah"); 20 | assert.ok(false); 21 | }; 22 | 23 | exports['test_skip2'] = function(test, assert) { 24 | assert.ok(true); 25 | 26 | setTimeout(function() { 27 | test.skip(); 28 | assert.equal(1, 3); 29 | }, 10); 30 | }; 31 | -------------------------------------------------------------------------------- /example/test-init-function.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | var path = require('path'); 19 | var fs = require('fs'); 20 | 21 | var dirPath = path.join(process.cwd(), 'example/test-123456'); 22 | 23 | exports['test_folder_exists'] = function(test, assert) { 24 | fs.mkdir(dirPath, 0655, function(err) { 25 | assert.ifError(err); 26 | test.finish(); 27 | }); 28 | }; 29 | -------------------------------------------------------------------------------- /example/test-setup-fail.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | exports['setUp'] = function(test, assert) { 19 | assert.ok(false); 20 | test.finish(); 21 | }; 22 | 23 | exports['test_1'] = function(test, assert) { 24 | assert.ok(false); 25 | test.finish(); 26 | }; 27 | 28 | exports['test_2'] = function(test, assert) { 29 | assert.ok(false); 30 | test.finish(); 31 | }; 32 | -------------------------------------------------------------------------------- /example/test-scope-leaks.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | exports['test_scope_leaks_on_success'] = function(test, assert) { 19 | a = 1; 20 | b = 2 21 | c = 3; 22 | 23 | assert.ok(true); 24 | test.finish(); 25 | }; 26 | 27 | exports['test_scope_leaks_on_failure'] = function(test, assert) { 28 | d = 4; 29 | assert.ok(false); 30 | e = 5; 31 | 32 | test.finish(); 33 | }; 34 | -------------------------------------------------------------------------------- /example/test-print-stdout-stderr-timeout.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | exports['test_print_stdout_and_stderr_timeout'] = function(test, assert) { 19 | console.log('this is stdout 1'); 20 | console.error('this is stderr 1'); 21 | process.stdout.write('this is stdout 2'); 22 | process.stderr.write('this is stderr 2'); 23 | 24 | setTimeout(function() { 25 | test.finish(); 26 | }, 500000); 27 | }; 28 | -------------------------------------------------------------------------------- /example/test-initialize-and-finalize-function.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | var called = 0; 19 | 20 | exports['initialize'] = function(test, assert) { 21 | called++; 22 | test.finish(); 23 | }; 24 | 25 | exports['test_run'] = function(test, assert) { 26 | assert.equal(called, 1); 27 | called++; 28 | test.finish(); 29 | }; 30 | 31 | exports['finalize'] = function(test, assert) { 32 | assert.equal(called, 2); 33 | test.finish(); 34 | }; 35 | -------------------------------------------------------------------------------- /bin/whiskey-process-runner: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 4 | * contributor license agreements. See the NOTICE file distributed with 5 | * this work for additional information regarding copyright ownership. 6 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 7 | * (the "License"); you may not use this file except in compliance with 8 | * the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | var logmagic = require('logmagic'); 20 | 21 | var run = require('./../lib/process_runner/run').run; 22 | 23 | logmagic.route('whiskey.process_runner.*', logmagic.INFO, 'process_runner'); 24 | run(process.argv); 25 | 26 | process.on('uncaughtException', function(err) { 27 | console.log(err.message); 28 | 29 | if (err.stack) { 30 | console.log(err.stack); 31 | } 32 | 33 | process.exit(1); 34 | }); 35 | -------------------------------------------------------------------------------- /lib/reporters/coverage/base.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | function CoverageReporter(tests, options) { 19 | this._options = options; 20 | this._tests = tests; 21 | } 22 | 23 | exports.CoverageReporter = CoverageReporter; 24 | 25 | CoverageReporter.prototype.handleTestFileComplete = function(filePath, coverageObj) { 26 | throw new Error('Not implemented'); 27 | }; 28 | 29 | CoverageReporter.prototype.handleTestsComplete = function() { 30 | throw new Error('Not implemented'); 31 | }; 32 | 33 | -------------------------------------------------------------------------------- /example/test-success.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | var n = 0; 19 | 20 | exports['test_true_equals_true'] = function(test, assert) { 21 | assert.ok(true); 22 | 23 | setTimeout(function() { 24 | n++; 25 | test.finish(); 26 | }, 100); 27 | }; 28 | 29 | exports['test_two_plus_two_equals_four'] = function(done, assert) { 30 | assert.equal(2 + 2, 4); 31 | done(); 32 | }; 33 | 34 | exports['tearDown'] = function(test, assert) { 35 | assert.ok(n === 1); 36 | test.finish(); 37 | }; 38 | -------------------------------------------------------------------------------- /lib/gen_makefile.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | 4 | var async = require('async'); 5 | var sprintf = require('sprintf').sprintf; 6 | var templates = require('magic-templates'); 7 | templates.setTemplatesDir(path.join(__dirname, '../assets/')); 8 | templates.setDebug(false); 9 | 10 | /** 11 | * Generate and write a Makefile with Whiskey related targets. 12 | * @param {Array} testFiles Test files. 13 | * @param {String} targetPath path where a generated Makefile is saved. 14 | * @param {Function} callback Callback called with (err). 15 | */ 16 | function generateMakefile(testFiles, targetPath, callback) { 17 | var template = new templates.Template('Makefile.magic'); 18 | var fullPath = path.join(targetPath, 'Makefile'); 19 | var context = { 20 | test_files: testFiles.join(' \\\n ') 21 | }; 22 | 23 | if (path.existsSync(fullPath)) { 24 | callback(new Error(sprintf('File "%s" already exists', fullPath))); 25 | return; 26 | } 27 | 28 | async.waterfall([ 29 | template.load.bind(template), 30 | 31 | function render(template, callback) { 32 | template.render(context, callback); 33 | }, 34 | 35 | function save(output, callback) { 36 | fs.writeFile(fullPath, output.join(''), callback); 37 | } 38 | ], callback); 39 | } 40 | 41 | exports.generateMakefile = generateMakefile; 42 | -------------------------------------------------------------------------------- /lib/scopeleaks.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | function getSnapshot(scope) { 19 | var snapshot = {}; 20 | 21 | for (var item in scope) { 22 | snapshot[item] = true; 23 | } 24 | 25 | return snapshot; 26 | } 27 | 28 | function getDifferences(scopeSnapshot1, scopeSnapshot2) { 29 | var diff = []; 30 | 31 | for (var item in scopeSnapshot2) { 32 | if (scopeSnapshot1[item] === undefined) { 33 | diff.push(item); 34 | } 35 | } 36 | 37 | return diff; 38 | } 39 | 40 | exports.getSnapshot = getSnapshot; 41 | exports.getDifferences = getDifferences; 42 | -------------------------------------------------------------------------------- /example/test-timeout.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | exports['test_success_1'] = function(test, assert) { 19 | assert.ok(true); 20 | test.finish(); 21 | }; 22 | 23 | exports['test_success_2'] = function(test, assert) { 24 | assert.ok(true); 25 | test.finish(); 26 | }; 27 | 28 | exports['test_success_3'] = function(test, assert) { 29 | assert.ok(true); 30 | test.finish(); 31 | }; 32 | 33 | exports['test_test_timeout_non_blocking'] = function(test, assert) { 34 | setTimeout(function() { 35 | assert.ok(true); 36 | test.finish(); 37 | }, 50000); 38 | 39 | assert.ok(true); 40 | }; 41 | -------------------------------------------------------------------------------- /lib/errors.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | var util = require('util'); 20 | 21 | /** 22 | * A class which represents a test error. 23 | * 24 | * @param {String} errName Error name. 25 | * @param {Error} err Original error object. 26 | * 27 | * @constructor 28 | */ 29 | function TestError(errName, err) { 30 | this.errName = errName; 31 | 32 | if (err) { 33 | this.message = err.message; 34 | this.stack = err.stack; 35 | this.arguments = err.arguments; 36 | this.type = err.type; 37 | } 38 | } 39 | 40 | util.inherits(TestError, Error); 41 | 42 | exports.TestError = TestError; 43 | -------------------------------------------------------------------------------- /example/test-setup-and-teardown.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | exports['setUp'] = function(test, assert) { 19 | test.finish(); 20 | }; 21 | 22 | exports['test_indexOf'] = function(test, assert) { 23 | assert.equal('test'.indexOf('test'), 0); 24 | setTimeout(function() { 25 | test.finish(); 26 | }, 50); 27 | }; 28 | 29 | exports['test_throw'] = function(test, assert) { 30 | try { 31 | throw new Error('test'); 32 | } 33 | catch (err) { 34 | assert.ok(err); 35 | } 36 | 37 | test.finish(); 38 | }; 39 | 40 | exports['tearDown'] = function(test, assert) { 41 | test.finish(); 42 | }; 43 | -------------------------------------------------------------------------------- /example/some-file.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | var throwException = function() { 19 | throw new Error('failure!'); 20 | }; 21 | 22 | var throwNestedFunctions = function() { 23 | var function1 = function(callback) { 24 | var function2 = function(callback) { 25 | var function3 = function(callback) { 26 | throw new Error('woho, failure!'); 27 | }; 28 | 29 | function3(callback); 30 | }; 31 | 32 | function2(callback); 33 | }; 34 | 35 | function1(function() {}); 36 | }; 37 | 38 | exports.throwException = throwException; 39 | exports.throwNestedFunctions = throwNestedFunctions; 40 | -------------------------------------------------------------------------------- /example/test-bdd-failures.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | var bdd = require('../lib/bdd').init(exports); 19 | var describe = bdd.describe; 20 | 21 | describe('the bdd expect()', function(it) { 22 | 23 | it('correctly fails toBeNull()', function(expect) { 24 | expect("not null").toBeNull(); 25 | }); 26 | 27 | it('correctly fails toBeDefined()', function(expect) { 28 | expect(undefined).toBeDefined(); 29 | }); 30 | 31 | it('correctly fails toBeUndefined()', function(expect) { 32 | expect(true).toBeUndefined(); 33 | }); 34 | 35 | it('correctly fails toMatch()', function(expect) { 36 | expect('fish').toMatch(/not a fish/); 37 | }); 38 | 39 | }); 40 | -------------------------------------------------------------------------------- /example/test-getFilePathAndPattern.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | var common = require('../lib/common'); 19 | 20 | exports['test_getTestFilePathAndPattern'] = function(test, assert) { 21 | var i, len, item; 22 | var data = [ 23 | [ 'foo.js', [ 'foo.js', '*' ] ], 24 | [ '/bar/foo.js', [ '/bar/foo.js', '*' ] ], 25 | [ 'example.foo.', [ 'example/foo.js', '*'] ], 26 | [ 'example.foo.bar*', [ 'example/foo.js', 'bar*'] ], 27 | [ 'a.b?e*', [ 'a.js', 'b?e*'] ], 28 | 29 | ]; 30 | 31 | for (i = 0, len = data.length; i < len; i++) { 32 | item = data[i]; 33 | assert.deepEqual(common.getTestFilePathAndPattern(item[0]), item[1]); 34 | } 35 | 36 | test.finish(); 37 | }; 38 | -------------------------------------------------------------------------------- /example/test-leaks.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var assert = require('../lib/assert').getAssertModule(); 4 | var exec = require('child_process').exec; 5 | 6 | var sprintf = require('sprintf').sprintf; 7 | var async = require('async'); 8 | 9 | var cwd = process.cwd(); 10 | var cmd1 = sprintf('%s/bin/whiskey --tests %s/example/test-scope-leaks.js ' + 11 | '--sequential --scope-leaks --timeout 2000', cwd, cwd); 12 | 13 | var cmd2 = sprintf('%s/bin/whiskey --tests %s/example/test-scope-leaks.js ' + 14 | '--concurrency 100 --scope-leaks --timeout 2000', cwd, cwd); 15 | 16 | async.series([ 17 | function testSequentialMode(callback) { 18 | exec(cmd1, function(err, stdout, stderr) { 19 | try { 20 | assert.match(stdout, /leaked variables/i); 21 | assert.match(stdout, /test_scope_leaks_on_success: a, b, c[^,]/i); 22 | assert.match(stdout, /test_scope_leaks_on_failure: d[^,]/i); 23 | } 24 | catch (err2) { 25 | callback(err2); 26 | return; 27 | } 28 | 29 | callback(); 30 | }); 31 | }, 32 | 33 | function testParallelMode(callback) { 34 | exec(cmd2, function(err, stdout, stderr) { 35 | try { 36 | assert.match(stdout, /leaked variables/i); 37 | assert.match(stdout, /test-scope-leaks\.js:/i); 38 | } 39 | catch (err2) { 40 | callback(err2); 41 | return; 42 | } 43 | 44 | callback(); 45 | }); 46 | }], 47 | 48 | function(err) { 49 | if (err) { 50 | process.exit(3); 51 | } 52 | else { 53 | process.exit(0); 54 | } 55 | }); 56 | -------------------------------------------------------------------------------- /lib/reporters/scope-leaks/base.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | function ScopeLeaksReporter(tests, options) { 19 | this._tests = tests; 20 | this._options = options; 21 | 22 | this._leakedVariables = {}; 23 | } 24 | 25 | ScopeLeaksReporter.prototype.handleTestEnd = function(filePath, resultObj) { 26 | if (!this._leakedVariables.hasOwnProperty(filePath)) { 27 | this._leakedVariables[filePath] = {}; 28 | } 29 | this._leakedVariables[filePath][resultObj['name']] = resultObj['leaked_variables']; 30 | }; 31 | 32 | ScopeLeaksReporter.prototype.handleTestFileComplete = function(filePath, stdout, stderr) { 33 | throw new Error('Not implemented'); 34 | }; 35 | 36 | ScopeLeaksReporter.prototype.handleTestsComplete = function(filePath, stdout, stderr) { 37 | throw new Error('Not implemented'); 38 | }; 39 | 40 | exports.ScopeLeaksReporter = ScopeLeaksReporter; 41 | -------------------------------------------------------------------------------- /bin/whiskey: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 4 | * contributor license agreements. See the NOTICE file distributed with 5 | * this work for additional information regarding copyright ownership. 6 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 7 | * (the "License"); you may not use this file except in compliance with 8 | * the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | var path = require('path'); 20 | 21 | var logmagic = require('logmagic'); 22 | 23 | var cwd = process.cwd(); 24 | var argv = process.argv; 25 | var whiskey = require('../index'); 26 | var run = require('../lib/run'); 27 | var argvLen = argv.length; 28 | 29 | if (argvLen >= 3) { 30 | if (argv[2].indexOf('-') !== 0) { 31 | argv.splice(2, 0, '--tests'); 32 | } 33 | } 34 | 35 | logmagic.route('whiskey.process_runner.*', logmagic.INFO, 'process_runner'); 36 | whiskey.run(cwd, argv); 37 | 38 | process.on('uncaughtException', function(err) { 39 | console.log(err.message); 40 | 41 | if (err.stack) { 42 | console.log(err.stack); 43 | } 44 | 45 | run.exitCode = 1; 46 | 47 | if (run.processRunner) { 48 | run.processRunner.stop(process.exit.bind(null, 1)); 49 | } 50 | else { 51 | process.exit(1); 52 | } 53 | }); 54 | -------------------------------------------------------------------------------- /example/test-custom-assert-methods.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | 3 | var async = require('async'); 4 | 5 | exports['test_assert.response'] = function(test, assert) { 6 | var server = http.createServer(function (req, res) { 7 | if (req.method === 'GET') { 8 | res.writeHead(200, {'Content-Type': 'text/plain'}); 9 | } 10 | else { 11 | res.writeHead(404, {'Content-Type': 'text/plain'}); 12 | } 13 | 14 | res.end('hello world'); 15 | }); 16 | 17 | async.series([ 18 | function testGet(callback) { 19 | var req = { 20 | 'url': '/test', 21 | 'method': 'get' 22 | }; 23 | 24 | assert.response(server, req, function(res) { 25 | assert.equal(res.statusCode, 200); 26 | assert.equal(res.body, 'hello world'); 27 | callback(); 28 | }); 29 | }, 30 | 31 | function testPost(callback) { 32 | var req = { 33 | 'url': '/test', 34 | 'method': 'post' 35 | }; 36 | 37 | assert.response(server, req, function(res) { 38 | assert.equal(res.statusCode, 404); 39 | assert.equal(res.body, 'hello world'); 40 | callback(); 41 | }); 42 | } 43 | ], 44 | 45 | function(err) { 46 | assert.ifError(err); 47 | test.finish(); 48 | }); 49 | }; 50 | 51 | exports['test_other_custom_asserts_functions'] = function(test, assert) { 52 | assert.isNull(null); 53 | assert.isNotNull(1); 54 | assert.isNotNull(false); 55 | assert.isNotNull(0); 56 | assert.isNotNull(-1); 57 | assert.isDefined(1); 58 | assert.type('a', 'string'); 59 | assert.type(function() {}, 'function'); 60 | assert.includes([1,2,3], 2); 61 | assert.length([1,2,3], 3); 62 | 63 | test.finish(); 64 | }; 65 | -------------------------------------------------------------------------------- /assets/jquery.isonscreen.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2010 2 | * @author Laurence Wheway 3 | * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) 4 | * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. 5 | * 6 | * @version 1.2.0 7 | */ 8 | (function($) { 9 | jQuery.extend({ 10 | isOnScreen: function(box, container) { 11 | //ensure numbers come in as intgers (not strings) and remove 'px' is it's there 12 | for(var i in box){box[i] = parseFloat(box[i])}; 13 | for(var i in container){container[i] = parseFloat(container[i])}; 14 | 15 | if(!container){ 16 | container = { 17 | left: $(window).scrollLeft(), 18 | top: $(window).scrollTop(), 19 | width: $(window).width(), 20 | height: $(window).height() 21 | } 22 | } 23 | 24 | if( box.left+box.width-container.left > 0 && 25 | box.left < container.width+container.left && 26 | box.top+box.height-container.top > 0 && 27 | box.top < container.height+container.top 28 | ) return true; 29 | return false; 30 | } 31 | }) 32 | 33 | 34 | jQuery.fn.isOnScreen = function (container) { 35 | for(var i in container){container[i] = parseFloat(container[i])}; 36 | 37 | if(!container){ 38 | container = { 39 | left: $(window).scrollLeft(), 40 | top: $(window).scrollTop(), 41 | width: $(window).width(), 42 | height: $(window).height() 43 | } 44 | } 45 | 46 | if( $(this).offset().left+$(this).width()-container.left > 0 && 47 | $(this).offset().left < container.width+container.left && 48 | $(this).offset().top+$(this).height()-container.top > 0 && 49 | $(this).offset().top < container.height+container.top 50 | ) return true; 51 | return false; 52 | } 53 | })(jQuery); 54 | -------------------------------------------------------------------------------- /example/test-uncaught.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | var someFile = require('./some-file'); 19 | 20 | exports['test_uncaught_exception'] = function(test, assert) { 21 | throw new Error('Testing uncaught exception'); 22 | // Line bellow won't be reached 23 | test.finish(); 24 | }; 25 | 26 | exports['test_uncaught_exception_2'] = function(test, assert) { 27 | someFile.throwException(); 28 | // Line bellow won't be reached 29 | test.finish(); 30 | }; 31 | 32 | exports['test_unknown_method'] = function(test, assert) { 33 | someFile.unknownMethod(); 34 | // Line bellow won't be reached 35 | test.finish(); 36 | }; 37 | 38 | exports['test_uncaught_nested_functions'] = function(test, assert) { 39 | someFile.throwNestedFunctions(); 40 | // Line bellow won't be reached 41 | test.finish(); 42 | }; 43 | 44 | exports['test1_require_throws'] = function(test, assert) { 45 | require('timeout-throws.js'); 46 | }; 47 | -------------------------------------------------------------------------------- /lib/extern/long-stack-traces/examples.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /lib/reporters/coverage/json.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | var path = require('path'); 19 | var fs = require('fs'); 20 | 21 | var coverage = require('./../../coverage'); 22 | var CoverageReporter = require('./base').CoverageReporter; 23 | 24 | function JSONReporter(tests, options) { 25 | CoverageReporter.call(this, tests, options); 26 | 27 | this._coverage = {}; 28 | this._coverageFile = this._options['file']; 29 | } 30 | 31 | JSONReporter.prototype.handleTestFileComplete = function(filePath, coverageObj) { 32 | var filename = filePath; 33 | this._coverage[filename] = coverageObj; 34 | }; 35 | 36 | JSONReporter.prototype.handleTestsComplete = function(coverageObj) { 37 | var filePath; 38 | var coverage = coverageObj || this._coverage; 39 | var data = JSON.stringify(coverage); 40 | 41 | if (this._coverageFile.indexOf('/') === 0) { 42 | filePath = this._coverageFile; 43 | } 44 | else { 45 | filePath = path.join(process.cwd(), this._coverageFile); 46 | } 47 | 48 | fs.writeFileSync(filePath, data, 'utf8'); 49 | }; 50 | 51 | exports.name = 'json'; 52 | exports.klass = JSONReporter; 53 | -------------------------------------------------------------------------------- /lib/reporters/test/base.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | function TestReporter(tests, options) { 19 | this._tests = tests; 20 | this._options = options; 21 | 22 | this._testResults = {}; 23 | } 24 | 25 | // Called before starting the tests 26 | TestReporter.prototype.handleTestsStart = function() { 27 | throw new Error('Not implemented'); 28 | }; 29 | 30 | TestReporter.prototype.handleTestEnd = function(filePath, resultObj) { 31 | if (!this._testResults.hasOwnProperty(filePath)) { 32 | this._testResults[filePath] = {}; 33 | } 34 | 35 | this._testResults[filePath][resultObj['name']] = resultObj; 36 | }; 37 | 38 | // Called when a new test process is spawned 39 | TestReporter.prototype.handleTestFileStart = function(filePath) { 40 | throw new Error('Not implemented'); 41 | }; 42 | 43 | // Called when the tests for the test file complete 44 | TestReporter.prototype.handleTestFileComplete = function(filePath, stdout, stderr) { 45 | throw new Error('Not implemented'); 46 | }; 47 | 48 | // Called at the end when all the tests complete 49 | TestReporter.prototype.handleTestsComplete = function() { 50 | throw new Error('Not implemented'); 51 | }; 52 | 53 | exports.TestReporter = TestReporter; 54 | -------------------------------------------------------------------------------- /example/test-bdd.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | var bdd = require('../lib/bdd').init(exports); 19 | var describe = bdd.describe; 20 | var beforeEach = bdd.beforeEach; 21 | 22 | var wasBeforeEachCalled = false; 23 | 24 | beforeEach(function() { 25 | wasBeforeEachCalled = true; 26 | }); 27 | 28 | describe('the bdd module', function(it) { 29 | 30 | it('supports it(), expect(), and toEqual()', function(expect) { 31 | expect(true).toEqual(true); 32 | }); 33 | 34 | it('supports beforeEach()', function(expect) { 35 | expect(wasBeforeEachCalled).toEqual(true); 36 | }); 37 | 38 | it('supports async tests', function(expect, callback) { 39 | var called = false; 40 | setTimeout(function() { 41 | called = true; 42 | }, 1); 43 | setTimeout(function() { 44 | expect(called).toEqual(true); 45 | callback(); 46 | }, 3); 47 | }); 48 | 49 | }); 50 | 51 | describe('the bdd expect()', function(it) { 52 | 53 | it('handles toBeNull()', function(expect) { 54 | expect(null).toBeNull(); 55 | }); 56 | 57 | it('handles toBeDefined()', function(expect) { 58 | expect(true).toBeDefined(); 59 | }); 60 | 61 | it('handles toBeUndefined()', function(expect) { 62 | expect(undefined).toBeUndefined(); 63 | }); 64 | 65 | it('handles toMatch()', function(expect) { 66 | expect('fish').toMatch(/is/); 67 | }); 68 | 69 | }); 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "whiskey", 3 | "description": "Whiskey is a powerful test runner for Node.js applications and a process orchestration framework which makes running integration tests with a lot of service / process dependencies easier.", 4 | "version": "0.8.5-dev", 5 | "author": "Cloudkick, Inc. http://www.cloudkick.com", 6 | "contributors": [ 7 | { 8 | "name": "Tomaz Muraus", 9 | "email": "tomaz+npm@tomaz.me", 10 | "url": "http://www.tomaz.me" 11 | }, 12 | { 13 | "name": "Russell Haering", 14 | "email": "russellhaering@gmail.com", 15 | "url": "http://russellhaering.com" 16 | }, 17 | { 18 | "name": "Bjorn Tipling", 19 | "email": "bjorn@ambientchill.com", 20 | "url": "http://bjorn.tipling.com" 21 | }, 22 | { 23 | "name": "Robert Chiniquy", 24 | "email": "rchiniquy@yahoo.com", 25 | "url": "http://robert-chiniquy.github.io" 26 | }, 27 | { 28 | "name": "Sam Falvo", 29 | "email": "sam.falvo@rackspace.com" 30 | } 31 | ], 32 | "keywords": [ "whiskey", "tests", "test runner", "testing", "tdd", "coverage", "test coverage", "process orchestration"], 33 | "homepage": "https://github.com/cloudkick/whiskey", 34 | "repository": { 35 | "type": "git", 36 | "url": "git://github.com/cloudkick/whiskey.git" 37 | }, 38 | "directories": { 39 | "lib": "./lib", 40 | "example": "./example", 41 | "bin": "./bin" 42 | }, 43 | "scripts": { 44 | "lint": "./scripts/lint.sh", 45 | "test": "make test" 46 | }, 47 | "dependencies": { 48 | "sprintf": ">= 0.1.1", 49 | "async": ">= 0.1.22", 50 | "magic-templates": "= 0.1.1", 51 | "rimraf": "= 1.0.1", 52 | "terminal": "= 0.1.3", 53 | "gex": "= 0.0.1", 54 | "simplesets": "= 1.1.6", 55 | "logmagic": "= 0.1.4", 56 | "underscore": ">= 1.4.2", 57 | "istanbul": ">= 0.1.37", 58 | "rewire": ">= 1.1.3", 59 | "required-as": ">= 0.0.0" 60 | }, 61 | "devDependencies": { 62 | "jshint": "2.1.3" 63 | }, 64 | "engines": { 65 | "node": ">= 0.8.0" 66 | }, 67 | "main": "./index", 68 | "bin": "./bin/whiskey", 69 | "licenses" : [ 70 | { 71 | "type" : "Apache", 72 | "url" : "http://www.apache.org/licenses/LICENSE-2.0.html" 73 | } 74 | ] 75 | } 76 | -------------------------------------------------------------------------------- /lib/extern/optparse/examples/browser-test.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | optparse.js example 7 | 8 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /lib/process_runner/run.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | var path = require('path'); 19 | 20 | var term = require('terminal'); 21 | var sprintf = require('sprintf').sprintf; 22 | 23 | var constants = require('./../constants'); 24 | var parser = require('./../parser'); 25 | var ProcessRunner = require('./runner').ProcessRunner; 26 | 27 | function getRunnerInstance(configPath) { 28 | var runner; 29 | 30 | try { 31 | runner = new ProcessRunner(configPath); 32 | } 33 | catch (err) { 34 | term.puts(sprintf('Failed to load config file: [bold]%s[/bold]', err.message)); 35 | process.exit(1); 36 | } 37 | 38 | return runner; 39 | } 40 | 41 | function run(argv) { 42 | var p, options, configPath, runner, names; 43 | 44 | p = parser.getParser(constants.PROCESS_RUNNER_OPTIONS); 45 | p.banner = 'Usage: process-runner --config dependencies.json [--verify|--run]'; 46 | options = parser.parseArgv(p, argv); 47 | 48 | configPath = options.config || path.join(process.cwd(), 'dependencies.json'); 49 | 50 | if (options['verify']) { 51 | runner = getRunnerInstance(configPath); 52 | term.puts('Config file looks OK.'); 53 | } 54 | else if (options['run']) { 55 | runner = getRunnerInstance(configPath); 56 | names = options['names'] ? options['names'].split(',') : null; 57 | 58 | runner.start(names, function(err) { 59 | if (err) { 60 | term.puts('Failed to start processes!'); 61 | term.puts(sprintf('Error: %s', err.message)); 62 | } 63 | else { 64 | term.puts('All processes started'); 65 | } 66 | }); 67 | } 68 | else if (!p._halted) { 69 | console.log(p.banner); 70 | } 71 | 72 | process.on('SIGINT', function onSigint() { 73 | term.puts('Stopping all processes...'); 74 | 75 | runner.stop(function() { 76 | term.puts('All processes stopped'); 77 | }); 78 | }); 79 | } 80 | 81 | exports.run = run; 82 | -------------------------------------------------------------------------------- /lib/extern/optparse/examples/nodejs-test.js: -------------------------------------------------------------------------------- 1 | // Import the optparse script 2 | var optparse = require('../lib/optparse'); 3 | 4 | // Define some options 5 | var SWITCHES = [ 6 | ['-i', '--include-file FILE', "Includes a file"], 7 | ['-p', '--print [MESSAGE]', "Prints an optional message on screen"], 8 | ['-d', '--debug', "Enables debug mode"], 9 | ['-H', '--help', "Shows this help section"], 10 | ['--date DATE', "A date. A date is expected E.G. 2009-01-14"], 11 | ['--number NUMBER', "A Number. Supported formats are 123, 123.123, 0xA123"], 12 | ['--other NAME', "No handler defined for this option. Will be handled by the wildcard handler."], 13 | ]; 14 | 15 | // Create a new OptionParser with defined switches 16 | var parser = new optparse.OptionParser(SWITCHES), print_summary = true, 17 | first_arg; 18 | parser.banner = 'Usage: nodejs-test.js [options]'; 19 | 20 | // Internal variable to store options. 21 | var options = { 22 | debug: true, 23 | files: [], 24 | number: undefined, 25 | date: undefined 26 | }; 27 | 28 | // Handle the first argument (switches excluded) 29 | parser.on(0, function(value) { 30 | first_arg = value; 31 | }); 32 | 33 | // Handle the --include-file switch 34 | parser.on('include-file', function(name, value) { 35 | options.files.push(value); 36 | }); 37 | 38 | // Handle the --print switch 39 | parser.on('print', function(name, value) { 40 | console.log('PRINT: ' + (value || 'No message entered')); 41 | }); 42 | 43 | // Handle the --date switch 44 | parser.on('date', function(name, value) { 45 | options.date = value; 46 | }); 47 | 48 | // Handle the --number switch 49 | parser.on('number', function(name, value) { 50 | options.number = value; 51 | }); 52 | 53 | // Handle the --debug switch 54 | parser.on('debug', function() { 55 | options.debug = true; 56 | }); 57 | 58 | // Handle the --help switch 59 | parser.on('help', function() { 60 | console.log(parser.toString()); 61 | print_summary = false; 62 | }); 63 | 64 | // Set a default handler 65 | parser.on('*', function(opt, value) { 66 | console.log('wild handler for ' + opt + ', value=' + value); 67 | }); 68 | 69 | // Parse command line arguments 70 | parser.parse(process.ARGV); 71 | 72 | if(print_summary) { 73 | console.log("First non-switch argument is: " + first_arg); 74 | 75 | // Output all files that was included. 76 | console.log("No of files to include: " + options.files.length); 77 | for(var i = 0; i < options.files.length; i++) { 78 | console.log("File [" + (i + 1) + "]:" + options.files[i]); 79 | } 80 | 81 | // Is debug-mode enabled? 82 | console.log("Debug mode is set to: " + options.debug); 83 | 84 | console.log("Number value is: " + options.number); 85 | console.log("Date value is: " + options.date); 86 | } 87 | -------------------------------------------------------------------------------- /lib/reporters/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | var fs = require('fs'); 19 | var path = require('path'); 20 | 21 | var sprintf = require('sprintf').sprintf; 22 | 23 | var TEST_REPORTERS = {}; 24 | var COVERAGE_REPORTERS = {}; 25 | var SCOPE_LEAKS_REPORTERS = {}; 26 | 27 | function getReporter(type, name, tests, options) { 28 | var reporters, availableReporters; 29 | if (type === 'test') { 30 | reporters = TEST_REPORTERS; 31 | } 32 | else if (type === 'coverage') { 33 | reporters = COVERAGE_REPORTERS; 34 | } 35 | else if (type === 'scope-leaks') { 36 | reporters = SCOPE_LEAKS_REPORTERS; 37 | } 38 | else { 39 | throw new Error(sprintf('Invalid reporter type: %s', type)); 40 | } 41 | 42 | availableReporters = Object.keys(reporters); 43 | 44 | if (availableReporters.indexOf(name) === -1) { 45 | throw new Error(sprintf('Invalid reporter: %s. Valid reporters are: %s', 46 | name, availableReporters.join(', '))); 47 | } 48 | 49 | return new reporters[name](tests, options); 50 | } 51 | 52 | function discoverReporters(reporters, reportersPath) { 53 | var i, files, file, filesLen, moduleName, exported; 54 | var fullPath = path.join(__dirname, reportersPath); 55 | files = fs.readdirSync(fullPath); 56 | 57 | filesLen = files.length; 58 | for (i = 0; i < filesLen; i++) { 59 | file = files[i]; 60 | moduleName = file.replace(/\.js$/, ''); 61 | exported = require(sprintf('./%s/%s', reportersPath, moduleName)); 62 | 63 | if (exported.name && exported.klass) { 64 | reporters[exported.name] = exported.klass; 65 | } 66 | } 67 | } 68 | 69 | if (Object.keys(TEST_REPORTERS).length === 0) { 70 | discoverReporters(TEST_REPORTERS, 'test'); 71 | } 72 | 73 | if (Object.keys(COVERAGE_REPORTERS).length === 0) { 74 | discoverReporters(COVERAGE_REPORTERS, 'coverage'); 75 | } 76 | 77 | if (Object.keys(SCOPE_LEAKS_REPORTERS).length === 0) { 78 | discoverReporters(SCOPE_LEAKS_REPORTERS, 'scope-leaks'); 79 | } 80 | 81 | exports.getReporter = getReporter; 82 | -------------------------------------------------------------------------------- /lib/reporters/scope-leaks/cli.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | var util = require('util'); 19 | var path = require('path'); 20 | 21 | var sprintf = require('sprintf').sprintf; 22 | 23 | var ScopeLeaksReporter = require('./base').ScopeLeaksReporter; 24 | 25 | function CliReporter(tests, options) { 26 | ScopeLeaksReporter.call(this, tests, options); 27 | } 28 | 29 | util.inherits(CliReporter, ScopeLeaksReporter); 30 | 31 | CliReporter.prototype.handleTestsComplete = function() { 32 | this._reportLeakedVariables(); 33 | }; 34 | 35 | CliReporter.prototype._reportLeakedVariables = function() { 36 | var testFilePath, testFile, tests, test, leakedVariables, leakedVariablesTest; 37 | 38 | console.log(''); 39 | console.log('\033[1mLeaked Variables\033[22m'); 40 | console.log(''); 41 | 42 | for (testFilePath in this._leakedVariables) { 43 | testFile = path.basename(testFilePath); 44 | tests = this._leakedVariables[testFilePath]; 45 | 46 | if (this._options['sequential']) { 47 | // Sequential mode was used so we can accurately report leaked variables for 48 | // each test separately. 49 | console.log(testFile); 50 | 51 | for (test in tests) { 52 | leakedVariables = ((tests[test] && tests[test].length > 0) ? tests[test] : null); 53 | 54 | if (leakedVariables && leakedVariables.length > 0) { 55 | console.log(sprintf(' %s: %s', test, leakedVariables.join(', '))); 56 | } 57 | else { 58 | console.log(sprintf(' %s: no leaks detected', test)); 59 | } 60 | } 61 | } 62 | else { 63 | leakedVariables = []; 64 | for (test in tests) { 65 | leakedVariablesTest = ((tests[test] && tests[test].length > 0) ? tests[test] : null); 66 | 67 | if (leakedVariablesTest) { 68 | leakedVariables = leakedVariables.concat(leakedVariablesTest); 69 | } 70 | } 71 | 72 | if (leakedVariables && leakedVariables.length > 0) { 73 | console.log(sprintf(' %s: %s', testFile, leakedVariables.join(', '))); 74 | } 75 | else { 76 | console.log(sprintf(' %s: no leaks detected', testFile)); 77 | } 78 | } 79 | } 80 | }; 81 | 82 | exports.name = 'cli'; 83 | exports.klass = CliReporter; 84 | -------------------------------------------------------------------------------- /lib/extern/long-stack-traces/README.md: -------------------------------------------------------------------------------- 1 | Long Stacktraces 2 | ================ 3 | 4 | Long stacktraces for V8 implemented in user-land JavaScript. Supports Chrome/Chromium and Node.js. 5 | 6 | Background 7 | ---------- 8 | 9 | A common problem when debugging event-driven JavaScript is stack traces are limited to a single "event", so it's difficult to trace the code path that caused an error. 10 | 11 | A contrived example (taken from the PDF referenced below): 12 | 13 | function f() { 14 | throw new Error('foo'); 15 | } 16 | 17 | setTimeout(f, Math.random()*1000); 18 | setTimeout(f, Math.random()*1000); 19 | 20 | Which one throws the first error? 21 | 22 | Node.js intended to fix this problem with a solution called "Long Stacktraces": http://nodejs.org/illuminati0.pdf 23 | 24 | But what if we wanted something like this in the browser? It turns out V8 already has everything needed to implement this in user-land JavaScript (although in a slightly hacky way). 25 | 26 | V8 has a [stack trace API](http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi) that allows custom formatting of textual stack trace representations. By wrapping any function that registers an asynchronous event callback (e.x. `setTimeout` and `addEventListener` in the browser) we can store the stack trace at the time of callback registration, and later append it to stack traces. This also works for multiple levels of events (a timeout or event registered within a timeout or event, etc). 27 | 28 | Usage 29 | ----- 30 | 31 | For Node.js install using `npm install long-stack-traces`. 32 | 33 | Simply include the "long-stack-traces.js" via a script tag or other method before any event listener or timeout registrations. In Node.js call `require("long-stack-traces")`. 34 | 35 | Stack traces from example above: 36 | 37 | Uncaught Error: foo 38 | at f (index.html:24:23) 39 | ---------------------------------------- 40 | at setTimeout 41 | at onload (index.html:28:40) 42 | Uncaught Error: foo 43 | at f (index.html:24:23) 44 | ---------------------------------------- 45 | at setTimeout 46 | at onload (index.html:27:40) 47 | 48 | Note one was from the timeout on line 27, the other on line 28. Events' stack traces are divided by a line of dashes. 49 | 50 | See examples.html for more examples, and run `node examples.js` for a Node.js example. 51 | 52 | Supported APIs 53 | -------------- 54 | 55 | Currently supports the following APIs: 56 | 57 | ### Chromium ### 58 | * `setTimeout` 59 | * `setInterval` 60 | * `addEventListener` 61 | * `XMLHttpRequest.onreadystatechange` (stack actually recorded upon `send()`) 62 | 63 | ### Node.js ### 64 | * `setTimeout` 65 | * `setInterval` 66 | * `EventEmitter.addListener` 67 | * `EventEmitter.on` 68 | * All APIs that use `EventEmitter` 69 | 70 | TODO 71 | ---- 72 | 73 | * Gracefully degrade in non-V8 environments. 74 | * Figure out what's up with these stack frames when throwing an exception from an input's event handler: 75 | -------------------------------------------------------------------------------- /lib/reporters/coverage/cli.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | /* 19 | * _reportCoverage is taken from expresso 20 | * which is MIT licensed. 21 | * Copyright(c) TJ Holowaychuk 22 | */ 23 | 24 | var util = require('util'); 25 | var path = require('path'); 26 | var fs = require('fs'); 27 | 28 | var rimraf = require('rimraf'); 29 | var templates = require('magic-templates'); 30 | 31 | var constants = require('./../../constants'); 32 | var utils = require('./../../util'); 33 | var coverage = require('./../../coverage'); 34 | var CoverageReporter = require('./base').CoverageReporter; 35 | 36 | function CliReporter(tests, options) { 37 | CoverageReporter.call(this, tests, options); 38 | 39 | this._coverage = {}; 40 | } 41 | 42 | CliReporter.prototype.handleTestFileComplete = function(filePath, coverageObj) { 43 | var filename = filePath; 44 | this._coverage[filename] = coverageObj; 45 | }; 46 | 47 | CliReporter.prototype.handleTestsComplete = function(coverageObj) { 48 | coverageObj = (!coverageObj) ? coverage.populateCoverage(null, this._coverage) : coverageObj; 49 | this._reportCoverage(coverageObj); 50 | }; 51 | 52 | CliReporter.prototype._reportCoverage = function(cov) { 53 | util.puts(''); 54 | util.puts('Test Coverage'); 55 | var sep = ' +------------------------------------------+----------+------+------+--------+', 56 | lastSep = ' +----------+------+------+--------+'; 57 | util.puts(sep); 58 | util.puts(' | filename | coverage | LOC | SLOC | missed |'); 59 | util.puts(sep); 60 | for (var name in cov.files) { 61 | var file = cov.files[name]; 62 | util.print(' | ' + utils.rpad(name, 40)); 63 | util.print(' | ' + utils.lpad(file.coverage, 8)); 64 | util.print(' | ' + utils.lpad(file.LOC, 4)); 65 | util.print(' | ' + utils.lpad(file.SLOC, 4)); 66 | util.print(' | ' + utils.lpad(file.totalMisses, 6)); 67 | util.print(' |\n'); 68 | } 69 | util.puts(sep); 70 | util.print(' ' + utils.rpad('', 40)); 71 | util.print(' | ' + utils.lpad(cov.coverage, 8)); 72 | util.print(' | ' + utils.lpad(cov.LOC, 4)); 73 | util.print(' | ' + utils.lpad(cov.SLOC, 4)); 74 | util.print(' | ' + utils.lpad(cov.totalMisses, 6)); 75 | util.print(' |\n'); 76 | util.puts(lastSep); 77 | }; 78 | 79 | exports.name = 'cli'; 80 | exports.klass = CliReporter; 81 | -------------------------------------------------------------------------------- /PROCESS_RUNNER.md: -------------------------------------------------------------------------------- 1 | # Process Runner 2 | 3 | Whiskey process runner can be used for starting and managing service 4 | dependencies for your tests. 5 | 6 | Example dependencies include, but are not limited to: 7 | 8 | * databases, 9 | * web servers, 10 | * other external services 11 | 12 | Note: All the processes defined in the configuration file are managed by Whiskey 13 | process runner which means they shouldn't deamonize and must run in the 14 | foreground. 15 | 16 | ## Configuration File 17 | 18 | `dependencies.json` file is used to specify all the dependencies for your tests. 19 | 20 | Example configuration file: 21 | 22 | ```javascript 23 | { 24 | "cassandra": { 25 | "cmd": ["/usr/local/bin/cassandra/", "-f"], 26 | "cwd": ["__dirname", ".."], 27 | "log_file": "cassandra.log", 28 | "wait_for": "socket", 29 | "wait_for_options": { 30 | "host": "127.0.0.1", 31 | "port": "1234" 32 | }, 33 | "timeout": 6000 34 | }, 35 | 36 | "api_server": { 37 | "cmd": ["bin/api-server"], 38 | "depends": ["cassandra"] 39 | }, 40 | 41 | "celery": { 42 | "cmd": ["celeryd", "-w", "5"], 43 | "wait_for": "stdout", 44 | "wait_for_options": { 45 | "string": "Celery has been started..." 46 | } 47 | } 48 | } 49 | ``` 50 | 51 | ### Option descriptions 52 | 53 | * `cmd` - command to run. 54 | * `cwd` - process working directory (defaults to the current working 55 | directory). 56 | * `log_file` - path to the file where the process stdout and stderr is saved. 57 | (defaults to `cwd/.log`) 58 | * `wait_for` - Condition which must be met before the process is considered as 59 | started (defaults to `none`). 60 | * `kill_script` - path to the shell script which will be executed instead of 61 | sending `SIGKILL` signal to the managed process when stopping it. 62 | * `timeout` - how long to wait for process to start (in ms) before erroring 63 | out (defaults to 10 seconds). 64 | * `depends` - Array of names of process dependencies. Name must match some other 65 | process defined in the configuration file. 66 | 67 | Valid `wait_for` values: 68 | 69 | * `none` - don't wait 70 | * `stdout` - wait for a string on process standard output or standard error 71 | * `socket` - wait until a connection on the provided ip and port is successfully 72 | established 73 | 74 | Valid options for `wait_for_options`: 75 | 76 | * `stdout` - `string` 77 | * `socket` - `host`, `port` 78 | 79 | ## Specifying Dependencies In the Test Files 80 | 81 | ``` javascript 82 | exports.dependencies = ['name1', 'name2']; 83 | ``` 84 | 85 | ## Process Manager Command Line Tool 86 | 87 | Command line tool can be used for: 88 | 89 | * Verifying that the configuration file is correct 90 | * Starting specified dependencies without running the tests. All the started 91 | processed are stopped on `SIGINT`. 92 | 93 | ### Usage 94 | 95 | ### Verify configuration file 96 | 97 | `whiskey-process-runner --configuration [file.json] --verify` 98 | 99 | ### Start all the processes defined in the configuration file 100 | 101 | `whiskey-process-runner --configuration [file.json] --run` 102 | 103 | ### Start some processes defined in the configuration file 104 | 105 | `whiskey-process-runner --configuration [file.json] --run --names process1,process2` 106 | -------------------------------------------------------------------------------- /lib/run_test_file.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | var path = require('path'); 19 | 20 | var sprintf = require('sprintf').sprintf; 21 | 22 | var common = require('./common'); 23 | var testUtil = require('./util'); 24 | var coverage = require('./coverage'); 25 | var constants = require('./constants'); 26 | 27 | var ARGUMENTS = { 28 | 'test_path': { 29 | 'pos': 2 30 | }, 31 | 'socket_path': { 32 | 'pos': 3 33 | }, 34 | 'cwd': { 35 | 'pos': 4 36 | }, 37 | 'lib_cov_dir': { 38 | 'pos': 5 39 | }, 40 | 'scope_leaks': { 41 | 'pos': 6 42 | }, 43 | 'chdir': { 44 | 'pos': 7 45 | }, 46 | 'custom_assert_module': { 47 | 'pos': 8 48 | }, 49 | 'init_file': { 50 | 'pos': 9 51 | }, 52 | 'timeout': { 53 | 'pos': 10, 54 | 'type': 'number' 55 | }, 56 | 'concurrency': { 57 | 'pos': 11, 58 | 'type': 'pos' 59 | }, 60 | 'pattern': { 61 | 'pos': 12 62 | }, 63 | }; 64 | 65 | if (process.argv.length < 6) { 66 | console.log('No enough argumentes provided'); 67 | process.exit(1); 68 | } 69 | 70 | var args = testUtil.parseArguments(ARGUMENTS, process.argv); 71 | 72 | var testPath = args['test_path']; 73 | var socketPath = args['socket_path']; 74 | var cwd = args['cwd']; 75 | var libCovDir = args['lib_cov_dir']; 76 | var covered = !!libCovDir; // hack 77 | var scopeLeaks = args['scope_leaks']; 78 | var chdir = args['chdir']; 79 | var customAssertModule = args['custom_assert_module']; 80 | var testInitFile = args['init_file']; 81 | var timeout = args['timeout']; 82 | var concurrency = args['concurrency']; 83 | var pattern = args['pattern']; 84 | 85 | if (customAssertModule) { 86 | var exportedFunctions = require(customAssertModule.replace(/$\.js/, '')); 87 | common.registerCustomAssertionFunctions(exportedFunctions); 88 | } 89 | 90 | if (chdir && path.existsSync(chdir)) { 91 | process.chdir(chdir); 92 | } 93 | 94 | var options = { 'cwd': cwd, 'socket_path': socketPath, 95 | 'init_file': testInitFile, 'timeout': timeout, 96 | 'concurrency': concurrency, 'scope_leaks': scopeLeaks, 97 | 'covered': covered, 98 | 'pattern': pattern}; 99 | var testFile = new common.TestFile(testPath, options, chdir); 100 | 101 | process.on('uncaughtException', function(err) { 102 | testFile.addUncaughtException(err); 103 | }); 104 | 105 | testFile.runTests(function onTestFileEnd() { 106 | if (libCovDir && typeof __coverage__ === 'object') { 107 | testFile._reportTestCoverage(__coverage__); 108 | } 109 | testFile._reportTestFileEnd(); 110 | }); 111 | -------------------------------------------------------------------------------- /assets/jquery.hotkeys.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery Hotkeys Plugin 3 | * Copyright 2010, John Resig 4 | * Dual licensed under the MIT or GPL Version 2 licenses. 5 | * 6 | * Based upon the plugin by Tzury Bar Yochay: 7 | * http://github.com/tzuryby/hotkeys 8 | * 9 | * Original idea by: 10 | * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/ 11 | */ 12 | 13 | (function(jQuery){ 14 | 15 | jQuery.hotkeys = { 16 | version: "0.8", 17 | 18 | specialKeys: { 19 | 8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause", 20 | 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home", 21 | 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", 22 | 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7", 23 | 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/", 24 | 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8", 25 | 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta" 26 | }, 27 | 28 | shiftNums: { 29 | "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&", 30 | "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<", 31 | ".": ">", "/": "?", "\\": "|" 32 | } 33 | }; 34 | 35 | function keyHandler( handleObj ) { 36 | // Only care when a possible input has been specified 37 | if ( typeof handleObj.data !== "string" ) { 38 | return; 39 | } 40 | 41 | var origHandler = handleObj.handler, 42 | keys = handleObj.data.toLowerCase().split(" "); 43 | 44 | handleObj.handler = function( event ) { 45 | // Don't fire in text-accepting inputs that we didn't directly bind to 46 | if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) || 47 | event.target.type === "text") ) { 48 | return; 49 | } 50 | 51 | // Keypress represents characters, not special keys 52 | var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ], 53 | character = String.fromCharCode( event.which ).toLowerCase(), 54 | key, modif = "", possible = {}; 55 | 56 | // check combinations (alt|ctrl|shift+anything) 57 | if ( event.altKey && special !== "alt" ) { 58 | modif += "alt+"; 59 | } 60 | 61 | if ( event.ctrlKey && special !== "ctrl" ) { 62 | modif += "ctrl+"; 63 | } 64 | 65 | // TODO: Need to make sure this works consistently across platforms 66 | if ( event.metaKey && !event.ctrlKey && special !== "meta" ) { 67 | modif += "meta+"; 68 | } 69 | 70 | if ( event.shiftKey && special !== "shift" ) { 71 | modif += "shift+"; 72 | } 73 | 74 | if ( special ) { 75 | possible[ modif + special ] = true; 76 | 77 | } else { 78 | possible[ modif + character ] = true; 79 | possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true; 80 | 81 | // "$" can be triggered as "Shift+4" or "Shift+$" or just "$" 82 | if ( modif === "shift+" ) { 83 | possible[ jQuery.hotkeys.shiftNums[ character ] ] = true; 84 | } 85 | } 86 | 87 | for ( var i = 0, l = keys.length; i < l; i++ ) { 88 | if ( possible[ keys[i] ] ) { 89 | return origHandler.apply( this, arguments ); 90 | } 91 | } 92 | }; 93 | } 94 | 95 | jQuery.each([ "keydown", "keyup", "keypress" ], function() { 96 | jQuery.event.special[ this ] = { add: keyHandler }; 97 | }); 98 | 99 | })( jQuery ); 100 | -------------------------------------------------------------------------------- /lib/parser.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | var util = require('util'); 19 | 20 | var optparse = require('./extern/optparse/lib/optparse'); 21 | 22 | var constants = require('./constants'); 23 | var run = require('./run'); 24 | 25 | var halt = function(parser) { 26 | parser.halt(parser); 27 | parser._halted = true; 28 | run.exitCode = 1; 29 | }; 30 | 31 | var getParser = function(options) { 32 | var switches = []; 33 | 34 | switches = switches.concat(constants.DEFAULT_OPTIONS); 35 | switches = switches.concat(options); 36 | var parser = new optparse.OptionParser(switches); 37 | parser._options = options; 38 | 39 | parser.on('help', function() { 40 | util.puts(parser.toString()); 41 | halt(parser); 42 | }); 43 | 44 | parser.on('version', function() { 45 | util.puts(constants.VERSION); 46 | halt(parser); 47 | }); 48 | 49 | parser.on(function(opt) { 50 | util.puts('No handler was defined for option: ' + opt); 51 | halt(parser); 52 | }); 53 | 54 | parser.on('*', function(opt, value) { 55 | util.puts('wild handler for ' + opt + ', value=' + value); 56 | halt(parser); 57 | }); 58 | 59 | return parser; 60 | }; 61 | 62 | var getParserOptionsObject = function(options) { 63 | var i, option, split, optionName, optionType; 64 | var optionsLen = options.length; 65 | var optionsObj = {}; 66 | 67 | for (i = 0; i < optionsLen; i++) { 68 | option = options[i][1]; 69 | split = option.split(' '); 70 | 71 | optionName = split[0].replace(/\-\-/, ''); 72 | if (split.length === 1) { 73 | optionType = 'boolean'; 74 | } 75 | else { 76 | optionType = 'value'; 77 | } 78 | 79 | optionsObj[optionName] = optionType; 80 | } 81 | 82 | return optionsObj; 83 | }; 84 | 85 | var parseArgv = function(parser, argv) { 86 | var optionName, optionType; 87 | var optionsObj = getParserOptionsObject(parser._options); 88 | var options = {}; 89 | 90 | function handleParserOption(optionName, optionType) { 91 | parser.on(optionName, function(opt, value) { 92 | if (optionType === 'boolean') { 93 | options[optionName] = true; 94 | } 95 | else if (optionType === 'value') { 96 | if (value) { 97 | options[optionName] = value; 98 | } 99 | } 100 | }); 101 | } 102 | 103 | for (optionName in optionsObj) { 104 | if (optionsObj.hasOwnProperty(optionName)) { 105 | optionType = optionsObj[optionName]; 106 | handleParserOption(optionName, optionType); 107 | } 108 | } 109 | 110 | parser.parse(argv); 111 | return options; 112 | }; 113 | 114 | exports.getParser = getParser; 115 | exports.parseArgv = parseArgv; 116 | -------------------------------------------------------------------------------- /assets/whiskey_source.magic: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {# IE8 rounds line-height incorrectly, and adding this emulateIE7 line makes it right! #} 6 | {# http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/7684445e-f080-4d8f-8529-132763348e21 #} 7 | 8 | Coverage for {{name}}: {{cov.coverage}}% 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 34 | 35 |
36 | 37 |

Hot-keys on this page

38 |
39 |

40 | r 41 | m   toggle line displays 42 |

43 |

44 | j 45 | k   next/prev highlighted chunk 46 |

47 |

48 | 0   (zero) top of page 49 |

50 |

51 | 1   (one) first highlighted chunk 52 |

53 |
54 |
55 | 56 |
57 | 58 | 59 | 64 | 69 | 70 |
60 | {% for line in markup %} 61 |

{{line.number}}

62 | {% endfor %} 63 |
65 | {% for line in markup %} 66 |

{% if line.annotate %}{{line.annotate}}{% endif %}{{line.source}} 

67 | {% endfor %} 68 |
71 |
72 | 73 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /lib/bdd.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | var sprintf = require('sprintf').sprintf; 19 | 20 | function Expect(actual) { 21 | this._actual = actual; 22 | } 23 | 24 | /** 25 | * @param {Object} imports The foreign exports object to add bdd tests to 26 | * @returns {Object} bdd A collection of test functions 27 | */ 28 | exports.init = function(imports) { 29 | var bdd = { 30 | '_suiteSetup': function() {} 31 | }; 32 | 33 | /** @param {Function} setup function to call before each suite */ 34 | bdd.beforeEach = function(setup) { 35 | bdd._suiteSetup = setup; 36 | }; 37 | 38 | /** @description creates a test suite 39 | * @param {string} title 40 | * @param {Function} suite 41 | */ 42 | bdd.describe = function(title, suite) { 43 | 44 | /** @description it() is the equivalent of a exports['test blah'] in the whiskey idiom. 45 | * This function, when called, adds a whiskey test to the imports object. 46 | * @param {string} name 47 | * @param {Function} spec(expect, callback) 48 | */ 49 | function it(name, spec) { 50 | var whiskeyName = sprintf("test %s %s", title, name); 51 | 52 | function expect(actual) { 53 | return new Expect(actual); 54 | } 55 | 56 | /** @description Re-binds the Expect test methods using the newly-injected assert. 57 | * @param {object} test the test object injected by whiskey 58 | * @param {object} assert the assert object injected by whiskey 59 | */ 60 | imports[whiskeyName] = function(test, assert) { 61 | 62 | // make the whiskey test and assert objects available to bdd tests 63 | bdd.test = test; 64 | bdd.assert = assert; 65 | 66 | /** @description maps an assert method to a bdd matcher 67 | * @param {string} assertion the name of a whiskey assert method 68 | * @param {string} bddName the name of the equivalent expect method 69 | */ 70 | function translateMatcher(assertion, bddName) { 71 | Expect.prototype[bddName] = function() { 72 | assert[assertion].bind(this, this._actual).apply(this, arguments); 73 | }; 74 | } 75 | 76 | // This must be done each time a test is created, 77 | // as test and assert are injected in each test function. 78 | translateMatcher('equal', 'toEqual'); 79 | translateMatcher('isNull', 'toBeNull'); 80 | translateMatcher('isDefined', 'toBeDefined'); 81 | translateMatcher('isUndefined', 'toBeUndefined'); 82 | translateMatcher('match', 'toMatch'); 83 | 84 | spec(expect, test.finish); 85 | if (spec.length === 1) { 86 | // if spec isn't expecting test.finish as an async callback, call it directly 87 | test.finish(); 88 | } 89 | }; 90 | } 91 | 92 | bdd._suiteSetup(); 93 | suite(it); 94 | }; 95 | 96 | return bdd; 97 | }; 98 | -------------------------------------------------------------------------------- /assets/whiskey.magic: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Coverage report 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 25 | 26 |
27 | 28 |

Hot-keys on this page

29 |
30 |

31 | n 32 | s 33 | m 34 | {% if arcs %} 35 | b 36 | p 37 | {% endif %} 38 | c   change column sorting 39 |

40 |
41 |
42 | 43 |
44 | 45 | 46 | {# The title='' attr doesn't work in Safari. #} 47 | 48 | 49 | 50 | 51 | {% if arcs %} 52 | 53 | 54 | {% endif %} 55 | 56 | 57 | 58 | {# HTML syntax requires thead, tfoot, tbody #} 59 | 60 | 61 | 62 | 63 | 64 | {% if arcs %} 65 | 66 | 67 | {% endif %} 68 | 69 | 70 | 71 | 72 | {% for file in cov.files %} 73 | 74 | 75 | 76 | 77 | {% if arcs %} 78 | 79 | 80 | {% endif %} 81 | 82 | 83 | {% endfor %} 84 | 85 |
Modulestatementsmissingbranchespartialcoverage
Total{{cov.totalHits}}{{cov.totalMisses}}{{totals.n_branches}}{{totals.n_missing_branches}}{{cov.coverage}}%
{{file.name}}{{file.totalHits}}{{file.totalMisses}}{{file.nums.n_branches}}{{file.nums.n_missing_branches}}{{file.coverage}}%
86 |
87 | 88 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /example/test-spyon.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | /** 19 | * @constructor 20 | */ 21 | function Foo () { 22 | /** 23 | * @param {bool} 24 | */ 25 | this.fooCalled = false; 26 | /** 27 | * @param {string} 28 | */ 29 | this.bar = ""; 30 | /** 31 | * @param {string} 32 | */ 33 | this.postFix = 'postFix'; 34 | } 35 | 36 | Foo.prototype.setFoo = function () { 37 | this.fooCalled = true; 38 | }; 39 | 40 | /** 41 | * @param {string} bar 42 | */ 43 | Foo.prototype.setBar = function (bar) { 44 | this.bar = bar; 45 | return bar + this.postFix; 46 | }; 47 | 48 | exports['test_spyon_calls_method'] = function(test, assert) { 49 | var foo = new Foo(); 50 | test.spy.on('setFoo', foo); 51 | foo.setFoo(); 52 | assert.equal(foo.fooCalled, true); 53 | test.spy.clear('setFoo', foo); 54 | test.finish(); 55 | }; 56 | 57 | exports['test_spyon_returns_right'] = function(test, assert) { 58 | var foo, str; 59 | foo = new Foo(); 60 | str = 'foo'; 61 | test.spy.on('setBar', foo); 62 | assert.equal(foo.setBar(str), str + foo.postFix); 63 | test.spy.clear('setBar', foo); 64 | test.finish(); 65 | }; 66 | 67 | exports['test_spyon_count_correct'] = function(test, assert) { 68 | var foo = new Foo(); 69 | test.spy.on('setFoo', foo); 70 | foo.setFoo(); 71 | foo.setFoo(); 72 | foo.setFoo(); 73 | foo.setFoo(); 74 | foo.setFoo(); 75 | foo.setFoo(); 76 | foo.setFoo(); 77 | assert.equal(test.spy.called('setFoo'), 7); 78 | test.spy.clear('setFoo', foo); 79 | test.finish(); 80 | }; 81 | 82 | exports['test_spyon_with_arguments_correct'] = function(test, assert) { 83 | var foo, arr; 84 | foo = new Foo(); 85 | arr = ['a', 'b', null]; 86 | test.spy.on('setBar', foo); 87 | foo.setBar(arr); 88 | assert.ok(test.spy.called('setBar').withArgs(arr)); 89 | test.spy.clear('setBar', foo); 90 | test.finish(); 91 | }; 92 | 93 | exports['test_spyon_with_arguments_alias'] = function(test, assert) { 94 | var foo, arr; 95 | foo = new Foo(); 96 | arr = ['a', 'b', null]; 97 | test.spy.on('setBar', foo); 98 | foo.setBar(arr); 99 | assert.ok(test.spy.called('setBar').with(arr)); 100 | test.spy.clear('setBar', foo); 101 | test.finish(); 102 | }; 103 | 104 | exports['test_spyon_with_arguments_incorrect'] = function(test, assert) { 105 | var foo, arr; 106 | foo = new Foo(); 107 | arr = ['a', 'b', null]; 108 | test.spy.on('setBar', foo); 109 | foo.setBar(['c']); 110 | assert.ok(!test.spy.called('setBar').withArgs(arr)); 111 | test.spy.clear('setBar', foo); 112 | test.finish(); 113 | }; 114 | 115 | exports['test_spyon_accepts_function'] = function(test, assert) { 116 | var foo, func, set; 117 | foo = new Foo(); 118 | set = false; 119 | func = function () { 120 | set = true; 121 | } 122 | test.spy.on('setFoo', foo, func); 123 | foo.setFoo(); 124 | assert.equal(set, true); 125 | test.spy.clear('setFoo', foo); 126 | test.finish(); 127 | }; 128 | 129 | exports['test_spyon_reapplies_original_function'] = function(test, assert) { 130 | var foo, func; 131 | foo = new Foo(); 132 | func = foo.setFoo; 133 | test.spy.on('setFoo', foo); 134 | test.spy.clear('setFoo', foo); 135 | assert.equal(foo.setFoo, func); 136 | test.finish(); 137 | }; 138 | 139 | exports['test_spyon_reapplies_given_function'] = function(test, assert) { 140 | var foo, func, set; 141 | foo = new Foo(); 142 | set = false; 143 | func = function () { 144 | set = true; 145 | }; 146 | test.spy.on('setFoo', foo); 147 | test.spy.clear('setFoo', foo, func); 148 | assert.equal(foo.setFoo, func); 149 | test.finish(); 150 | }; 151 | 152 | exports['test_spyon_reset'] = function(test, assert) { 153 | var foo = new Foo(); 154 | test.spy.on('setFoo', foo); 155 | foo.setFoo(); 156 | test.spy.reset('setFoo'); 157 | assert.equal(test.spy.called('setFoo'), 0); 158 | test.spy.clear('setFoo', foo); 159 | test.finish(); 160 | }; 161 | 162 | -------------------------------------------------------------------------------- /lib/reporters/test/tap.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | var util = require('util'); 19 | var path = require('path'); 20 | 21 | var sprintf = require('sprintf').sprintf; 22 | 23 | var TestReporter = require('./base').TestReporter; 24 | var testUtil = require('./../../util'); 25 | 26 | function TapReporter(tests, options) { 27 | TestReporter.call(this, tests, options); 28 | 29 | this._tempResults = {}; 30 | this._successes = 0; 31 | this._failures = 0; 32 | this._timeouts = 0; 33 | } 34 | 35 | util.inherits(TapReporter, TestReporter); 36 | 37 | TapReporter.prototype.handleTestsStart = function() { 38 | }; 39 | 40 | TapReporter.prototype.handleTestFileStart = function(filePath) { 41 | }; 42 | 43 | TapReporter.prototype.handleTestFileComplete = function(filePath, 44 | stdout, stderr) { 45 | var tests, test, key; 46 | var fileName = path.basename(filePath); 47 | 48 | stdout = stdout || ''; 49 | stderr = stderr || ''; 50 | tests = this._testResults[filePath]; 51 | 52 | for (key in tests) { 53 | if (tests.hasOwnProperty(key)) { 54 | test = tests[key]; 55 | 56 | if (test.error) { 57 | this._addFailure(fileName, test.name); 58 | } 59 | else if (test.timeout) { 60 | this._addTimeout(fileName, 'timeout'); 61 | } 62 | else if (test.status === 'skipped') { 63 | continue; 64 | } 65 | else { 66 | this._addSuccess(fileName, test.name); 67 | } 68 | } 69 | } 70 | /*if (error) { 71 | // Test file does not exist or an exception was thrown before the tests were 72 | // even run 73 | this._addFailure(fileName, error.name); 74 | return; 75 | }*/ 76 | }; 77 | 78 | TapReporter.prototype.handleTestsComplete = function() { 79 | this._reportResults(); 80 | 81 | return this._failures + this._timeouts; 82 | }; 83 | 84 | TapReporter.prototype._addSuccess = function(testFile, testName) { 85 | this._successes++; 86 | this._addResult(testFile, testName, 'success'); 87 | }; 88 | 89 | TapReporter.prototype._addFailure = function(testFile, testName) { 90 | this._failures++; 91 | this._addResult(testFile, testName, 'failure'); 92 | }; 93 | 94 | TapReporter.prototype._addTimeout = function(testFile, testName) { 95 | this._timeouts++; 96 | this._addResult(testFile, testName, 'timeout'); 97 | }; 98 | 99 | TapReporter.prototype._addResult = function(testFile, testName, testStatus) { 100 | if (!this._tempResults.hasOwnProperty(testFile)) { 101 | this._tempResults[testFile] = {}; 102 | } 103 | 104 | this._tempResults[testFile][testName] = { 105 | 'file': testFile, 106 | 'name': testName, 107 | 'status': testStatus 108 | }; 109 | }; 110 | 111 | TapReporter.prototype._reportResults = function() { 112 | var self = this; 113 | var startNum, i, files, file, filesLen, testsLen, tests, test, testResult, testNum; 114 | files = Object.keys(this._tempResults); 115 | filesLen = files.length; 116 | 117 | function getFileTestLen(file) { 118 | return Object.keys(self._tempResults[file]).length; 119 | } 120 | 121 | testsLen = files.map(getFileTestLen).reduce(function (a, b) { return a + b; }); 122 | 123 | if (testsLen === 0) { 124 | startNum = 0; 125 | } 126 | else { 127 | startNum = 1; 128 | } 129 | 130 | console.log('%d..%d', startNum, testsLen); 131 | 132 | testNum = 0; 133 | for (i = 0; i < filesLen; i++) { 134 | file = files[i]; 135 | tests = this._tempResults[file]; 136 | 137 | for (test in tests) { 138 | if (tests.hasOwnProperty(test)) { 139 | testNum++; 140 | testResult = tests[test]; 141 | 142 | if (testResult.status === 'success') { 143 | console.log('ok %d - %s: %s', testNum, testResult.file, testResult.name); 144 | } 145 | else if (testResult.status === 'failure') { 146 | console.log('not ok %d - %s: %s', testNum, testResult.file, testResult.name); 147 | } 148 | else if (testResult.status === 'timeout') { 149 | console.log('not ok %d - %s: %s (timeout)', testNum, testResult.file, 150 | testResult.name); 151 | } 152 | } 153 | } 154 | } 155 | }; 156 | 157 | exports.name = 'tap'; 158 | exports.klass = TapReporter; 159 | -------------------------------------------------------------------------------- /lib/reporters/coverage/html.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | var util = require('util'); 19 | var path = require('path'); 20 | var fs = require('fs'); 21 | 22 | var rimraf = require('rimraf'); 23 | var templates = require('magic-templates'); 24 | 25 | var constants = require('./../../constants'); 26 | var coverage = require('./../../coverage'); 27 | var CoverageReporter = require('./base').CoverageReporter; 28 | 29 | function HtmlReporter(tests, options) { 30 | CoverageReporter.call(this, tests, options); 31 | 32 | if (!options['directory']) { 33 | throw new Error('Missing coverage-dir option'); 34 | } 35 | 36 | this._coverage = {}; 37 | 38 | this._coverageDirectory = this._options['directory']; 39 | this._assetsDirectory = path.join(__dirname, '../../../', 'assets'); 40 | 41 | templates.setTemplatesDir(this._assetsDirectory); 42 | templates.setDebug(false); 43 | } 44 | 45 | HtmlReporter.prototype.handleTestFileComplete = function(filePath, coverageObj) { 46 | var filename = filePath; 47 | this._coverage[filename] = coverageObj; 48 | }; 49 | 50 | HtmlReporter.prototype.handleTestsComplete = function(coverageObj) { 51 | coverageObj = (!coverageObj) ? coverage.populateCoverage(null, this._coverage) : coverageObj; 52 | this._writeCoverage(coverageObj); 53 | }; 54 | 55 | HtmlReporter.prototype._writeCoverage = function(cov) { 56 | var self = this; 57 | 58 | /* Remove output directory */ 59 | rimraf(self._coverageDirectory, function(err) { 60 | fs.mkdir(self._coverageDirectory, 0755, function() { 61 | self._writeFile(cov); 62 | self._writeSourceFiles(cov); 63 | self._writeStaticFiles(self._coverageDirectory); 64 | }); 65 | }); 66 | }; 67 | 68 | HtmlReporter.prototype._writeFile = function(cov) { 69 | var self = this; 70 | var template = new templates.Template('whiskey.magic'); 71 | 72 | var context = { 73 | version: constants.VERSION, 74 | coverage: cov.coverage, 75 | cov: cov 76 | }; 77 | 78 | template.load(function(err, template) { 79 | if (err) { 80 | // load/parse errors (invalid filename, bad template syntax) 81 | console.log(err); 82 | } 83 | else { 84 | template.render(context, function(err, output) { 85 | if (err) { 86 | // render errors (invalid filename in context variables, bad context variables) 87 | console.log(err); 88 | } 89 | else { 90 | fs.writeFile(path.join(self._coverageDirectory, 'index.html'), 91 | output.join('')); 92 | } 93 | }); 94 | } 95 | }); 96 | }; 97 | 98 | HtmlReporter.prototype._writeSourceFiles = function(cov) { 99 | var self = this; 100 | var template = new templates.Template('whiskey_source.magic'); 101 | 102 | template.load(function(err, template) { 103 | if (err) { 104 | // load/parse errors (invalid filename, bad template syntax) 105 | console.log(err); 106 | } 107 | else { 108 | for (var name in cov.files) { 109 | var context = { 110 | version: constants.VERSION, 111 | name: name, 112 | cov: cov.files[name], 113 | markup: self._generateSourceMarkup(cov.files[name]) 114 | }; 115 | 116 | template.render(context, function(err, output) { 117 | if (err) { 118 | // render errors (invalid filename in context variables, bad context variables) 119 | console.log(err); 120 | } 121 | else { 122 | fs.writeFile(path.join(self._coverageDirectory, 123 | cov.files[name].htmlName), 124 | output.join('')); 125 | } 126 | }); 127 | } 128 | } 129 | }); 130 | }; 131 | 132 | HtmlReporter.prototype._generateSourceMarkup = function(cov) { 133 | var rv = [], _class, data, source; 134 | 135 | for (var i = 1, linesLen = (cov.source.length + 1); i < linesLen; i++) { 136 | data = cov.lines[i.toString()]; 137 | _class = 'pln'; 138 | if (data !== null) { 139 | if (parseInt(data, 10) > 0) { 140 | _class = 'stm run'; 141 | } 142 | else if (parseInt(data, 10) === 0) { 143 | _class = 'stm mis'; 144 | } 145 | } 146 | source = cov.source[i-1]; 147 | source = source.replace(/\s/g, ' '); 148 | rv.push({number: i, css: _class, source: source}); 149 | } 150 | 151 | return rv; 152 | }; 153 | 154 | HtmlReporter.prototype._writeStaticFiles = function(dest) { 155 | this._copyAsset('style.css', dest); 156 | this._copyAsset('coverage_html.js', dest); 157 | this._copyAsset('jquery-1.4.3.min.js', dest); 158 | this._copyAsset('jquery.tablesorter.min.js', dest); 159 | this._copyAsset('jquery.isonscreen.js', dest); 160 | this._copyAsset('jquery.hotkeys.js', dest); 161 | this._copyAsset('keybd_closed.png', dest); 162 | this._copyAsset('keybd_open.png', dest); 163 | }; 164 | 165 | HtmlReporter.prototype._copyAsset = function(asset, dest) { 166 | this._copyFile(path.join(this._assetsDirectory, asset), 167 | path.join(dest, asset)); 168 | }; 169 | 170 | HtmlReporter.prototype._copyFile = function(src, dst) { 171 | var oldFile = fs.createReadStream(src); 172 | var newFile = fs.createWriteStream(dst); 173 | 174 | newFile.once('open', function(fd) { 175 | util.pump(oldFile, newFile); 176 | }); 177 | }; 178 | 179 | exports.name = 'html'; 180 | exports.klass = HtmlReporter; 181 | -------------------------------------------------------------------------------- /assets/style.css: -------------------------------------------------------------------------------- 1 | /* CSS styles for Coverage. */ 2 | /* Page-wide styles */ 3 | html, body, h1, h2, h3, p, td, th { 4 | margin: 0; 5 | padding: 0; 6 | border: 0; 7 | outline: 0; 8 | font-weight: inherit; 9 | font-style: inherit; 10 | font-size: 100%; 11 | font-family: inherit; 12 | vertical-align: baseline; 13 | } 14 | 15 | /* Set baseline grid to 16 pt. */ 16 | body { 17 | font-family: georgia, serif; 18 | font-size: 1em; 19 | } 20 | 21 | html>body { 22 | font-size: 16px; 23 | } 24 | 25 | /* Set base font size to 12/16 */ 26 | p { 27 | font-size: .75em; /* 12/16 */ 28 | line-height: 1.3333em; /* 16/12 */ 29 | } 30 | 31 | table { 32 | border-collapse: collapse; 33 | } 34 | 35 | a.nav { 36 | text-decoration: none; 37 | color: inherit; 38 | } 39 | a.nav:hover { 40 | text-decoration: underline; 41 | color: inherit; 42 | } 43 | 44 | /* Page structure */ 45 | #header { 46 | background: #f8f8f8; 47 | width: 100%; 48 | border-bottom: 1px solid #eee; 49 | } 50 | 51 | #source { 52 | padding: 1em; 53 | font-family: "courier new", monospace; 54 | } 55 | 56 | #indexfile #footer { 57 | margin: 1em 3em; 58 | } 59 | 60 | #pyfile #footer { 61 | margin: 1em 1em; 62 | } 63 | 64 | #footer .content { 65 | padding: 0; 66 | font-size: 85%; 67 | font-family: verdana, sans-serif; 68 | color: #666666; 69 | font-style: italic; 70 | } 71 | 72 | #index { 73 | margin: 1em 0 0 3em; 74 | } 75 | 76 | /* Header styles */ 77 | #header .content { 78 | padding: 1em 3em; 79 | } 80 | 81 | h1 { 82 | font-size: 1.25em; 83 | } 84 | 85 | h2.stats { 86 | margin-top: .5em; 87 | font-size: 1em; 88 | } 89 | .stats span { 90 | border: 1px solid; 91 | padding: .1em .25em; 92 | margin: 0 .1em; 93 | cursor: pointer; 94 | border-color: #999 #ccc #ccc #999; 95 | } 96 | .stats span.hide_run, .stats span.hide_exc, 97 | .stats span.hide_mis, .stats span.hide_par, 98 | .stats span.par.hide_run.hide_par { 99 | border-color: #ccc #999 #999 #ccc; 100 | } 101 | .stats span.par.hide_run { 102 | border-color: #999 #ccc #ccc #999; 103 | } 104 | 105 | /* Help panel */ 106 | #keyboard_icon { 107 | float: right; 108 | cursor: pointer; 109 | } 110 | 111 | .help_panel { 112 | position: absolute; 113 | background: #ffc; 114 | padding: .5em; 115 | border: 1px solid #883; 116 | display: none; 117 | } 118 | 119 | #indexfile .help_panel { 120 | width: 20em; height: 4em; 121 | } 122 | 123 | #pyfile .help_panel { 124 | width: 16em; height: 8em; 125 | } 126 | 127 | .help_panel .legend { 128 | font-style: italic; 129 | margin-bottom: 1em; 130 | } 131 | 132 | #panel_icon { 133 | float: right; 134 | cursor: pointer; 135 | } 136 | 137 | .keyhelp { 138 | margin: .75em; 139 | } 140 | 141 | .keyhelp .key { 142 | border: 1px solid black; 143 | border-color: #888 #333 #333 #888; 144 | padding: .1em .35em; 145 | font-family: monospace; 146 | font-weight: bold; 147 | background: #eee; 148 | } 149 | 150 | /* Source file styles */ 151 | .linenos p { 152 | text-align: right; 153 | margin: 0; 154 | padding: 0 .5em; 155 | color: #999999; 156 | font-family: verdana, sans-serif; 157 | font-size: .625em; /* 10/16 */ 158 | line-height: 1.6em; /* 16/10 */ 159 | } 160 | .linenos p.highlight { 161 | background: #ffdd00; 162 | } 163 | .linenos p a { 164 | text-decoration: none; 165 | color: #999999; 166 | } 167 | .linenos p a:hover { 168 | text-decoration: underline; 169 | color: #999999; 170 | } 171 | 172 | td.text { 173 | width: 100%; 174 | } 175 | .text p { 176 | margin: 0; 177 | padding: 0 0 0 .5em; 178 | border-left: 2px solid #ffffff; 179 | white-space: nowrap; 180 | } 181 | 182 | .text p.mis { 183 | background: #ffdddd; 184 | border-left: 2px solid #ff0000; 185 | } 186 | .text p.run, .text p.run.hide_par { 187 | background: #ddffdd; 188 | border-left: 2px solid #00ff00; 189 | } 190 | .text p.exc { 191 | background: #eeeeee; 192 | border-left: 2px solid #808080; 193 | } 194 | .text p.par, .text p.par.hide_run { 195 | background: #ffffaa; 196 | border-left: 2px solid #eeee99; 197 | } 198 | .text p.hide_run, .text p.hide_exc, .text p.hide_mis, .text p.hide_par, 199 | .text p.hide_run.hide_par { 200 | background: inherit; 201 | } 202 | 203 | .text span.annotate { 204 | font-family: georgia; 205 | font-style: italic; 206 | color: #666; 207 | float: right; 208 | padding-right: .5em; 209 | } 210 | .text p.hide_par span.annotate { 211 | display: none; 212 | } 213 | 214 | /* Syntax coloring */ 215 | .text .com { 216 | color: green; 217 | font-style: italic; 218 | line-height: 1px; 219 | } 220 | .text .key { 221 | font-weight: bold; 222 | line-height: 1px; 223 | } 224 | .text .str { 225 | color: #000080; 226 | } 227 | 228 | /* index styles */ 229 | #index td, #index th { 230 | text-align: right; 231 | width: 5em; 232 | padding: .25em .5em; 233 | border-bottom: 1px solid #eee; 234 | } 235 | #index th { 236 | font-style: italic; 237 | color: #333; 238 | border-bottom: 1px solid #ccc; 239 | cursor: pointer; 240 | } 241 | #index th:hover { 242 | background: #eee; 243 | border-bottom: 1px solid #999; 244 | } 245 | #index td.left, #index th.left { 246 | padding-left: 0; 247 | } 248 | #index td.right, #index th.right { 249 | padding-right: 0; 250 | } 251 | #index th.headerSortDown, #index th.headerSortUp { 252 | border-bottom: 1px solid #000; 253 | } 254 | #index td.name, #index th.name { 255 | text-align: left; 256 | width: auto; 257 | } 258 | #index td.name a { 259 | text-decoration: none; 260 | color: #000; 261 | } 262 | #index td.name a:hover { 263 | text-decoration: underline; 264 | color: #000; 265 | } 266 | #index tr.total { 267 | } 268 | #index tr.total td { 269 | font-weight: bold; 270 | border-top: 1px solid #ccc; 271 | border-bottom: none; 272 | } 273 | #index tr.file:hover { 274 | background: #eeeeee; 275 | } 276 | -------------------------------------------------------------------------------- /lib/reporters/test/cli.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | var util = require('util'); 19 | var path = require('path'); 20 | 21 | var sprintf = require('sprintf').sprintf; 22 | var terminal = require('terminal'); 23 | 24 | var TestReporter = require('./base').TestReporter; 25 | var testUtil = require('./../../util'); 26 | 27 | function CliReporter(tests, options) { 28 | TestReporter.call(this, tests, options); 29 | 30 | this._dateStart = null; 31 | this._dateEnd = null; 32 | this._successes = 0; 33 | this._failures = 0; 34 | this._timeouts = 0; 35 | this._skipped = 0; 36 | 37 | this._puts = (this._options['styles']) ? terminal.puts : terminal.putsNoStyle; 38 | } 39 | 40 | util.inherits(CliReporter, TestReporter); 41 | 42 | CliReporter.prototype.handleTestsStart = function() { 43 | this._dateStart = testUtil.getUnixTimestamp(); 44 | }; 45 | 46 | CliReporter.prototype.handleTestFileStart = function(filePath) { 47 | this._puts(filePath); 48 | }; 49 | 50 | CliReporter.prototype.handleTestFileComplete = function(filePath, stdout, stderr) { 51 | var tests, test, timing, key, runTime = 0, testCount = 0; 52 | 53 | stdout = stdout || ''; 54 | stderr = stderr || ''; 55 | tests = this._testResults[filePath]; 56 | 57 | for (key in tests) { 58 | if (tests.hasOwnProperty(key)) { 59 | test = tests[key]; 60 | timing = {'start': (test.time_start || 0), 'end': (test.time_end || 0)}; 61 | 62 | runTime += (timing.end - timing.start); 63 | testCount++; 64 | 65 | if (test.error) { 66 | this._failures++; 67 | this._reportFailure(test.name, test.error, timing); 68 | } 69 | else if (test.timeout) { 70 | this._timeouts++; 71 | this._reportTimeout(); 72 | } 73 | else if (test.status === 'skipped') { 74 | this._skipped++; 75 | this._reportSkipped(test.name, test.skip_msg, timing); 76 | } 77 | else { 78 | this._successes++; 79 | this._reportSuccess(test.name, timing); 80 | } 81 | } 82 | } 83 | 84 | if (this._options['report_timing']) { 85 | this._reportTestStatistics(testCount, runTime); 86 | } 87 | 88 | if (this._failures > 0 || this._timeouts > 0) { 89 | this._printStderr(stderr); 90 | this._printStdout(stdout); 91 | } 92 | else { 93 | if (this._options['print_stderr']) { 94 | this._printStderr(stderr); 95 | } 96 | 97 | if (this._options['print_stdout']) { 98 | this._printStdout(stdout); 99 | } 100 | } 101 | }; 102 | 103 | CliReporter.prototype.handleTestsComplete = function() { 104 | this._dateEnd = testUtil.getUnixTimestamp(); 105 | this._reportStatistics(); 106 | 107 | return (this._failures + this._timeouts); 108 | }; 109 | 110 | CliReporter.prototype._reportTestStatistics = function(testCount, runTime) { 111 | this._puts(''); 112 | this._puts(sprintf('Ran %d tests in %0.3fs', testCount, (runTime / 1000))); 113 | }; 114 | 115 | CliReporter.prototype._printStdout = function(stdout) { 116 | if (stdout.length > 0) { 117 | this._puts(''); 118 | this._puts('[bold]Stdout[/bold]:'); 119 | this._puts(stdout); 120 | } 121 | }; 122 | 123 | CliReporter.prototype._printStderr = function(stdout) { 124 | if (stdout.length > 0) { 125 | this._puts(''); 126 | this._puts('[bold]Stderr[/bold]:'); 127 | this._puts(stdout); 128 | } 129 | }; 130 | 131 | CliReporter.prototype._getTestNameWithTiming = function(testName, timing) { 132 | var str; 133 | 134 | if (!this._options['report_timing']) { 135 | return testName; 136 | } 137 | 138 | return sprintf('%s (took %0.3fs)', testName, ((timing.end - timing.start) / 1000)); 139 | }; 140 | 141 | CliReporter.prototype._reportSuccess = function(testName, timing) { 142 | testName = this._getTestNameWithTiming(testName, timing); 143 | this._puts(sprintf(' %s [green][OK][/green]', 144 | testUtil.addCharacters(testName, 74, ' '))); 145 | }; 146 | 147 | CliReporter.prototype._reportFailure = function(testName, error, timing) { 148 | testName = this._getTestNameWithTiming(testName, timing); 149 | var errMsg = (error.stack) ? error.stack : error.message; 150 | this._puts(sprintf(' %s [red][FAIL][/red]', 151 | testUtil.addCharacters(testName, 72, ' '))); 152 | this._puts(''); 153 | this._puts('[bold]Exception[/bold]'); 154 | this._puts(errMsg); 155 | this._puts(''); 156 | }; 157 | 158 | CliReporter.prototype._reportTimeout = function() { 159 | this._puts(sprintf(' %s [cyan][TIMEOUT][/cyan]', 160 | testUtil.addCharacters('timeout', 69, ' '))); 161 | }; 162 | 163 | CliReporter.prototype._reportSkipped = function(testName, msg, timing) { 164 | msg = msg || ''; 165 | testName = (msg) ? sprintf('%s (%s)', testName, msg) : testName; 166 | this._puts(sprintf(' %s [grey][SKIPPED][/grey]', 167 | testUtil.addCharacters(testName, 69, ' '))); 168 | }; 169 | 170 | CliReporter.prototype._reportStatistics = function() { 171 | var runTime = (this._dateEnd - this._dateStart); 172 | var successes = this._successes; 173 | var failures = this._failures; 174 | var timeouts = this._timeouts; 175 | var skipped = this._skipped; 176 | 177 | this._puts(testUtil.addCharacters('', 81, '-')); 178 | this._puts(sprintf('Ran %d tests in %0.3fs', (successes + failures), runTime)); 179 | this._puts(''); 180 | this._puts(sprintf('Successes: %s', successes)); 181 | this._puts(sprintf('Failures: %s', failures)); 182 | this._puts(sprintf('Timeouts: %s', timeouts)); 183 | this._puts(sprintf('Skipped: %s', skipped)); 184 | this._puts(''); 185 | 186 | if (failures === 0 && timeouts === 0) { 187 | this._puts('[green]PASSED[/green]'); 188 | } 189 | else { 190 | this._puts('[red]FAILED[/red]'); 191 | } 192 | }; 193 | 194 | exports.name = 'cli'; 195 | exports.klass = CliReporter; 196 | -------------------------------------------------------------------------------- /lib/extern/optparse/README.md: -------------------------------------------------------------------------------- 1 | optparse-js 2 | =========== 3 | 4 | Optparse-js is a command line option parser for Javascript. It's slightly based on Ruby's implementation optparse but with some differences (different languages has different needs) such as custom parsers. 5 | 6 | All examples in this readme is using [Node.js](http://nodejs.org/). How ever, the library works with all kinds of Javascript implementations. 7 | 8 | 9 | QUICK START 10 | ----------- 11 | 12 | The library defines one class, the OptionParser class. The class constructor takes one single argument, a list with a set of rules. Here is a quick example: 13 | 14 | // Import the sys library 15 | var sys = require('sys'); 16 | 17 | // Import the optparse library. 18 | var optparse = require('optparse'); 19 | 20 | // Define an option called ´´help´´. We give it a quick alias named ´´-h´´ 21 | // and a quick help text. 22 | var switches = [ 23 | ['-h', '--help', 'Shows help sections'] 24 | ]; 25 | 26 | // Create a new OptionParser. 27 | var parser = new optparse.OptionParser(switches); 28 | 29 | // Hook the help option. The callback will be executed when the OptionParser 30 | // hits the switch ´´-h´´ or ´´--help´´. Each representatio 31 | parser.on('help', function() { 32 | sys.puts('Help'); 33 | }); 34 | 35 | 36 | 37 | DEFINING RULES 38 | -------------- 39 | The OptionParser constructor takes an Array with rules. Each rule is represented by an array (tuple) of two or three values. A typical rule definition may look like this: 40 | 41 | ['-h', '--help', 'Print this help'] 42 | 43 | 44 | The first value is optional, and represents an alias for the long-named switch (the second value, in this case ´´--help´´). 45 | 46 | The second argument is the actual rule. The rule must start with a double dash followed by a switch name (in this case ´help´). The OptionParser also supports special option arguments. Define an option argument in the rule by adding a named argument after the leading double dash and switch name (E.G '--port-number PORT_NUMBER'). The argument is then parsed to the option handler. To define an optional option argument, just add a braces around argument in the rule (E.G '--port-number [PORT_NUMBER]). The OptionParser also supports filter. More on that in in the section called ´Option Filters´. 47 | 48 | The third argument is an optional rule description. 49 | 50 | 51 | OPTION FILTERS 52 | -------------- 53 | Filters is a neat feature that let you filter option arguments. The OptionParser itself as already a set of built-in common filter's. These are: 54 | 55 | - NUMBER, supports both decimal and hexadecimal numbers. 56 | - DATE, filters arguments that matches YYYY-MM-DD. 57 | - EMAIL, filters arguments that matches my@email.com. 58 | 59 | It's simple to use any of the filter above in your rule-set. Here is a quick example how to filter number: 60 | 61 | var rules = [ 62 | ['--first-option NUMBER', 'Takes a number as argument'], 63 | ['--second-option [NUMBER]', 'Takes an optional number as argument'] 64 | ] 65 | 66 | You can add your own set of filter by calling the *parser_instance.filter* method: 67 | 68 | parser.filter('single_char', function(value) { 69 | if(value.length != 1) throw "Filter mismatch."; 70 | return value; 71 | }); 72 | 73 | 74 | OPTION PARSER 75 | ------------- 76 | The OptionParser class has the following properties and methods: 77 | 78 | ### string banner 79 | An optional usage banner. This text is included when calling ´´toString´´. Default value is: "Usage: [Options]". 80 | 81 | 82 | ### string options_title 83 | An optional title for the options list. This text is included when calling ´´toString´´. Default value is: "Available options:". 84 | 85 | 86 | ### function on(switch_or_arg_index, callback) 87 | Add's a callback for a switch or an argument (defined by index). Switch hooks MUST be typed witout the leading ´´--´´. This example show how to hook a switch: 88 | 89 | parser.on('help', function(optional_argument) { 90 | // Show help section 91 | }); 92 | 93 | And this example show how to hook a positional argument (an option without the leading - or --). 94 | Note that positional argument 0 will be "node" and positional argument 1 will be the path of the 95 | script being run. Positional argument 2 will be the first positional argument after the script path: 96 | 97 | parser.on(2, function(opt) { 98 | puts('The first non-switch option is:' + opt); 99 | }); 100 | 101 | It's also possible to define a default handler. The default handler is called when no rule's are meet. Here is an example how to add a ´default handler´: 102 | 103 | parser.on(function(opt) { 104 | puts('No handler was defined for option:' + opt); 105 | }); 106 | 107 | Use the wildcard handler to build a custom ´´on´´ handler. 108 | 109 | parser.on('*', function(opt, value) { 110 | puts('option=' + opt + ', value=' + value); 111 | }); 112 | 113 | ### function filter(name, callback) 114 | Adds a new filter extension to the OptionParser instance. The first argument is the name of the filter (trigger). The second argument is the actual filter See the ´OPTION FILTERS´ section for more info. 115 | 116 | It's possible to override the default filters by passing the value "_DEFAULT" to the ´´name´´ argument. The name of the filter is automatically transformed into 117 | upper case. 118 | 119 | 120 | ### function halt([callback]) 121 | Interrupt's further parsing. This function should be called from an ´on´ -callbacks, to cancel the parsing. This can be useful when the program should ignore all other arguments (when displaying help or version information). 122 | 123 | The function also takes an optional callback argument. If the callback argument is specified, a ´halt´ callback will be added (instead of executing the ´halt´ command). 124 | 125 | Here is an example how to add an ´on_halt´ callback: 126 | 127 | parser.halt(function() { 128 | puts('An option callback interupted the parser'); 129 | }); 130 | 131 | 132 | ### function parse(arguments) 133 | Start's parsing of arguments. This should be the last thing you do. 134 | 135 | 136 | ### function options() 137 | Returns an Array with all defined option rules 138 | 139 | 140 | ### function toString() 141 | Returns a string representation of this OptionParser instance (a formatted help section). 142 | 143 | 144 | MORE EXAMPLES 145 | ------------- 146 | See examples/nodejs-test.js and examples/browser-test-html for more info how to 147 | use the script. 148 | 149 | 150 | SUGGESTIONS 151 | ----------- 152 | All comments in how to improve this library is very welcome. Feel free post suggestions to the [Issue tracker](http://github.com/jfd/optparse-js/issues), or even better, fork the repository to implement your own features. 153 | 154 | 155 | LICENSE 156 | ------- 157 | Released under a MIT-style license. 158 | 159 | 160 | COPYRIGHT 161 | --------- 162 | Copyright (c) 2009 Johan Dahlberg 163 | 164 | -------------------------------------------------------------------------------- /lib/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | var sprintf = require('sprintf').sprintf; 19 | 20 | /* 21 | * How long to wait for a test file to complete. 22 | */ 23 | var DEFAULT_TEST_TIMEOUT = 15 * 1000; 24 | 25 | var DEFAULT_CONCURRENCY = 1; 26 | 27 | /** 28 | * Default reporters. 29 | */ 30 | var DEFAULT_TEST_REPORTER = 'cli'; 31 | var DEFAULT_COVERAGE_REPORTER = 'cli'; 32 | var DEFAULT_SCOPE_LEAKS_REPORTER = 'cli'; 33 | 34 | /** 35 | * Program version. 36 | */ 37 | var VERSION = '0.8.3'; 38 | 39 | /** 40 | * Path where the instrumented files are saved. 41 | */ 42 | var COVERAGE_PATH = 'lib-cov'; 43 | 44 | /** 45 | * Default test verbosity (1 = quiet, 2 = verbose, 3 = very verbose) 46 | */ 47 | var DEFAULT_VERBOSITY = 2; 48 | 49 | var TEST_FILE_INITIALIZE_FUNCTION_NAME = 'initialize'; 50 | var TEST_FILE_FINALIZE_FUNCTION_NAME = 'finalize'; 51 | 52 | var INIT_FUNCTION_NAME = 'initialize'; 53 | 54 | var GLOBAL_TEARDOWN_FUNCTION_NAME = 'globalTearDown'; 55 | var TEARDOWN_FUNCTION_NAME = 'tearDown'; 56 | var GLOBAL_SETUP_FUNCTION_NAME = 'globalSetUp'; 57 | var SETUP_FUNCTION_NAME = 'setUp'; 58 | 59 | var DEFAULT_SOCKET_PATH = '/tmp/whiskey-parent.sock'; 60 | 61 | /** 62 | * Different markers. 63 | */ 64 | var TEST_START_MARKER = '@-start-test-@'; 65 | var DELIMITER = '@-delimiter-@'; 66 | var TEST_END_MARKER = '@-end-test-@'; 67 | var TEST_FILE_END_MARKER = '@-end-test-file-@'; 68 | var COVERAGE_END_MARKER = '@-end-coverage-@'; 69 | 70 | /** 71 | * Default switches for the option parser. 72 | */ 73 | var DEFAULT_OPTIONS = [ 74 | ['-h', '--help', 'Print this help'], 75 | ['-V', '--version', 'Print the version'] 76 | ]; 77 | 78 | /** 79 | * Other options for the Whiskey option parser. 80 | */ 81 | var WHISKEY_OPTIONS = [ 82 | ['-t', '--tests STRING', 'Whitespace separated list of test suites to run sequentially.'], 83 | ['-T', '--independent-tests STRING', 'Whitespace separated list of test suites capable of running independently and concurrently.'], 84 | ['-m', '--max-suites NUMBER', 'The number of concurrently executing test suites (defaults to 5)'], 85 | ['-ti', '--test-init-file STRING', 'An initialization file which is run before each test file'], 86 | ['-c', '--chdir STRING', 'Directory to which each test process chdirs before running the tests'], 87 | ['-v', '--verbosity [NUMBER]', 'Test runner verbosity'], 88 | ['-g', '--global-setup-teardown STRING', 'Specifies the file containing the globalSetUp and globalTearDown procedures.'], 89 | ['', '--failfast', 'Stop running the tests on the first failure'], 90 | ['', '--timeout [NUMBER]', 'How long to wait (ms) for a test file to complete before timing out'], 91 | ['', '--socket-path STRING', sprintf('A path to the unix socket used for communication. ' + 92 | 'Defaults to %s', DEFAULT_SOCKET_PATH)], 93 | 94 | ['', '--concurrency [NUMBER]', sprintf('Maximum number of tests in a file which will run in ' + 95 | 'parallel. Defaults to %s', DEFAULT_CONCURRENCY)], 96 | ['', '--sequential', 'Run test in a file in sequential mode. This is the same as using --concurrency 1'], 97 | 98 | ['', '--custom-assert-module STRING', 'Absolute path to a module with custom assert methods'], 99 | 100 | ['', '--no-styles', 'Don\'t use colors and styles'], 101 | 102 | ['', '--quiet', 'Don\'t print stdout and stderr'], 103 | ['', '--real-time', 'Print data which is sent to stdout / stderr as soon ' + 104 | 'as it comes in'], 105 | 106 | ['', '--test-reporter STRING', 'Rest reporter type (cli or tap)'], 107 | 108 | ['', '--coverage', 'Enable test coverage'], 109 | ['', '--coverage-file STRING', 'A file where the coverage result is stored. Only has an effect if json coverage reporter is used'], 110 | ['', '--coverage-files STRING', 'A comma-separated list of files containing coverage data'], 111 | ['', '--coverage-reporter STRING', 'Coverage reporter type (cli, html)'], 112 | ['', '--coverage-dir STRING', 'Directory where the HTML coverage report is saved'], 113 | ['', '--coverage-exclude STRING', 'Paths which won\'t be instrumented'], 114 | ['', '--coverage-no-instrument STRING', 'Copy but don\'t instrument the path'], 115 | ['', '--coverage-no-regen', 'Don\'t generate coverage file if lib-cov directory already exists'], 116 | 117 | ['', '--scope-leaks', 'Records which variables were leaked into the global ' + 118 | 'scope.'], 119 | ['', '--scope-leaks-reporter STRING', 'Scope leaks reporter type (cli)'], 120 | ['-d', '--debug NUMBER', 'Attach a debugger to a test process on a specified port number'], 121 | ['', '--gen-makefile', 'Genarate a Makefile'], 122 | ['', '--makefile-path STRING', 'Path where a generated Makefile is saved'], 123 | ['', '--report-timing', 'Report test timing'], 124 | ['', '--dependencies STRING', 'Path to the test dependencies configuration file'], 125 | ['', '--only-essential-dependencies', 'Only start dependencies required by the tests files which are ran'] 126 | ]; 127 | 128 | /** 129 | * Other options for the Process runner option parser. 130 | */ 131 | var PROCESS_RUNNER_OPTIONS = [ 132 | ['-c', '--config STRING', 'Path to the dependencies.json file'], 133 | ['-v', '--verify', 'Verify the config'], 134 | ['-r', '--run', 'Run the processes'], 135 | ['-n', '--names STRING', 'Comma-delimited string of the processes to run. Only applicable' + 136 | ' if --run is used'] 137 | ]; 138 | 139 | exports.DEFAULT_TEST_TIMEOUT = DEFAULT_TEST_TIMEOUT; 140 | exports.DEFAULT_CONCURRENCY = DEFAULT_CONCURRENCY; 141 | exports.DEFAULT_TEST_REPORTER = DEFAULT_TEST_REPORTER; 142 | exports.DEFAULT_COVERAGE_REPORTER = DEFAULT_COVERAGE_REPORTER; 143 | exports.DEFAULT_SCOPE_LEAKS_REPORTER = DEFAULT_SCOPE_LEAKS_REPORTER; 144 | 145 | exports.VERSION = VERSION; 146 | exports.COVERAGE_PATH = COVERAGE_PATH; 147 | exports.DEFAULT_VERBOSITY = DEFAULT_VERBOSITY; 148 | exports.TEST_FILE_INITIALIZE_FUNCTION_NAME = TEST_FILE_INITIALIZE_FUNCTION_NAME; 149 | exports.TEST_FILE_FINALIZE_FUNCTION_NAME = TEST_FILE_FINALIZE_FUNCTION_NAME; 150 | exports.INIT_FUNCTION_NAME = INIT_FUNCTION_NAME; 151 | exports.SETUP_FUNCTION_NAME = SETUP_FUNCTION_NAME; 152 | exports.TEARDOWN_FUNCTION_NAME = TEARDOWN_FUNCTION_NAME; 153 | exports.GLOBAL_SETUP_FUNCTION_NAME = GLOBAL_SETUP_FUNCTION_NAME; 154 | exports.GLOBAL_TEARDOWN_FUNCTION_NAME = GLOBAL_TEARDOWN_FUNCTION_NAME; 155 | exports.DEFAULT_SOCKET_PATH = DEFAULT_SOCKET_PATH; 156 | exports.DEFAULT_OPTIONS = DEFAULT_OPTIONS; 157 | exports.WHISKEY_OPTIONS = WHISKEY_OPTIONS; 158 | exports.PROCESS_RUNNER_OPTIONS = PROCESS_RUNNER_OPTIONS; 159 | 160 | exports.TEST_START_MARKER = TEST_START_MARKER; 161 | exports.DELIMITER = DELIMITER; 162 | exports.TEST_END_MARKER = TEST_END_MARKER; 163 | exports.TEST_FILE_END_MARKER = TEST_FILE_END_MARKER; 164 | exports.COVERAGE_END_MARKER = COVERAGE_END_MARKER; 165 | -------------------------------------------------------------------------------- /lib/coverage.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | /* 19 | * coverage and populateCoverage are taken from expresso 20 | * which is MIT licensed. 21 | * Copyright(c) TJ Holowaychuk 22 | */ 23 | 24 | var path = require('path'); 25 | var fs = require('fs'); 26 | 27 | /* 28 | * Convert an Istanbul __coverage__ object so it's JSON serializable 29 | */ 30 | function jscoverageFromIstanbulCoverage(coverage, chdir) { 31 | var jscov = {}, file, filename, hitCount, line, sid; 32 | 33 | chdir = chdir || process.cwd(); 34 | 35 | for (file in coverage) { 36 | filename = path.relative(chdir, file); 37 | jscov[filename] = {}; 38 | jscov[filename]['source'] = fs.readFileSync(file).toString().split('\n'); 39 | 40 | for (line = 0; line < jscov[filename]['source'].length; ++line) { 41 | jscov[filename][line] = null; 42 | } 43 | 44 | for (sid in coverage[file].s) { 45 | if (coverage[file].s.hasOwnProperty(sid)) { 46 | hitCount = coverage[file].s[sid]; 47 | if (typeof(coverage[file].statementMap[sid]) !== 'undefined') { 48 | line = coverage[file].statementMap[sid].start.line; 49 | jscov[filename][line] = hitCount; 50 | } else { 51 | // somehow log an error here. Recommendations welcome. 52 | // Can this error ever happen in the first place? 53 | } 54 | } 55 | } 56 | } 57 | 58 | return jscov; 59 | } 60 | 61 | function stringifyCoverage(cov, chdir) { 62 | var i, files, file, fileObj, source; 63 | var tmp = {}, coverageObj = {}; 64 | 65 | cov = jscoverageFromIstanbulCoverage(cov, chdir); 66 | 67 | files = Object.keys(cov); 68 | 69 | for (i = 0; i < files.length; i++) { 70 | file = files[i]; 71 | fileObj = cov[file]; 72 | source = fileObj['source']; 73 | delete fileObj['source']; 74 | 75 | coverageObj[file] = { 76 | 'lines': fileObj, 77 | 'source': source 78 | }; 79 | } 80 | 81 | return JSON.stringify(coverageObj); 82 | } 83 | 84 | /** 85 | * Total coverage for the given file data. 86 | * 87 | * @param {Array} data 88 | * @return {Type} 89 | */ 90 | function coverage(data, type) { 91 | var comparisionFunc; 92 | var n = 0; 93 | 94 | function isCovered(val) { 95 | return (val > 0); 96 | } 97 | 98 | function isMissed(val) { 99 | return !isCovered(val); 100 | } 101 | 102 | if (type === 'covered') { 103 | comparisionFunc = isCovered; 104 | } 105 | else if (type === 'missed') { 106 | comparisionFunc = isMissed; 107 | } 108 | else { 109 | throw new Error('Invalid type: ' + type); 110 | } 111 | 112 | var len = Object.keys(data.lines).length; 113 | 114 | for (var i = 0; i < len; ++i) { 115 | if (data.lines[i] !== null && comparisionFunc(data.lines[i])) { 116 | ++n; 117 | } 118 | } 119 | 120 | return n; 121 | } 122 | 123 | function getEmptyResultObject() { 124 | var results = {}; 125 | 126 | results.LOC = 0; 127 | results.SLOC = 0; 128 | results.totalFiles = 0; 129 | results.totalHits = 0; 130 | results.totalMisses = 0; 131 | results.coverage = 0; 132 | results.files = {}; 133 | 134 | return results; 135 | } 136 | 137 | /** 138 | * @param {?Object} results Optional results object. If it's not provided it will be created. 139 | * @param {?Object} cov coverage object. 140 | */ 141 | function populateCoverage(results, cov) { 142 | var linesLen, testfile, name, file, fileStats; 143 | 144 | results = (!results) ? getEmptyResultObject() : results; 145 | 146 | /* Aggregate data from all files */ 147 | for (testfile in cov) { 148 | for (name in cov[testfile]) { 149 | file = cov[testfile][name]; 150 | 151 | fileStats = results.files[name] || { 152 | name: name, 153 | htmlName: name.replace('.js', '.html').replace('.java', '.html').replace(/\/|\\/g, '_'), 154 | totalHits: 0, 155 | totalMisses: 0, 156 | totalLines: 0, 157 | lines: null, 158 | source: null 159 | }; 160 | 161 | if (fileStats.lines === null) { 162 | fileStats.lines = file.lines; 163 | } 164 | else { 165 | linesLen = file.lines.length; 166 | for (var i = 0; i < linesLen; i++) { 167 | if (file.lines[i] !== null) { 168 | if (fileStats.lines[i] === null) { 169 | fileStats.lines[i] = file.lines[i]; 170 | } 171 | else { 172 | fileStats.lines[i] += file.lines[i]; 173 | } 174 | } 175 | } 176 | } 177 | 178 | if (fileStats.source === null) { 179 | fileStats.source = file.source; 180 | } 181 | 182 | results.files[name] = fileStats; 183 | } 184 | } 185 | 186 | /* Calculate statistics */ 187 | for (name in results.files) { 188 | fileStats = results.files[name]; 189 | 190 | // File level statistics 191 | fileStats.totalHits = coverage(fileStats, 'covered'); 192 | fileStats.totalMisses = coverage(fileStats, 'missed'); 193 | fileStats.totalLines = fileStats.totalHits + fileStats.totalMisses; 194 | fileStats.coverage = (fileStats.totalHits / fileStats.totalLines) * 100; 195 | fileStats.coverage = (isNaN(fileStats.coverage)) ? 0 : fileStats.coverage.toFixed(2); 196 | fileStats.LOC = fileStats.source.length; 197 | fileStats.SLOC = fileStats.totalLines; 198 | results.files[name] = fileStats; 199 | 200 | // Global statistic update 201 | results.totalHits += fileStats.totalHits; 202 | results.totalMisses += fileStats.totalMisses; 203 | results.totalFiles++; 204 | results.LOC += fileStats.LOC; 205 | results.SLOC += fileStats.SLOC; 206 | } 207 | 208 | /* Calculate covergage of tests */ 209 | results.coverage = (results.totalHits / results.SLOC) * 100; 210 | results.coverage = results.coverage.toFixed(2); 211 | 212 | return results; 213 | } 214 | 215 | /** 216 | * Read multiple coverage files and return aggregated coverage. 217 | */ 218 | function aggregateCoverage(files) { 219 | var i, len, file, content, results; 220 | var resultsObj = getEmptyResultObject(); 221 | 222 | for (i = 0, len = files.length; i < len; i++) { 223 | file = files[i]; 224 | content = JSON.parse(fs.readFileSync(file).toString()); 225 | resultsObj = populateCoverage(resultsObj, content); 226 | } 227 | 228 | return resultsObj; 229 | } 230 | 231 | 232 | 233 | function installCoverageHandler() { 234 | var pid = process.pid; 235 | var coverageDirectory = process.env['COVERAGE_DIRECTORY']; 236 | var coveragePath = path.join(coverageDirectory, pid + '.json'); 237 | 238 | function writeCoverage() { 239 | var coverage = {}; 240 | 241 | if (typeof __coverage__ === 'object') { 242 | coverage[pid] = JSON.parse(stringifyCoverage(__coverage__)); 243 | 244 | try { 245 | fs.writeFileSync(coveragePath, JSON.stringify(coverage), 'utf8'); 246 | } 247 | catch (e) {} 248 | } 249 | 250 | process.exit(); 251 | } 252 | 253 | if (coverageDirectory) { 254 | process.on('SIGUSR2', writeCoverage); 255 | } 256 | } 257 | 258 | exports.stringifyCoverage = stringifyCoverage; 259 | exports.populateCoverage = populateCoverage; 260 | exports.aggregateCoverage = aggregateCoverage; 261 | exports.installCoverageHandler = installCoverageHandler; 262 | -------------------------------------------------------------------------------- /lib/extern/long-stack-traces/lib/long-stack-traces.js: -------------------------------------------------------------------------------- 1 | (function(LST) { 2 | LST.rethrow = false; 3 | 4 | var currentTraceError = null; 5 | 6 | var filename = new Error().stack.split("\n")[1].match(/^ at ((?:\w+:\/\/)?[^:]+)/)[1]; 7 | function filterInternalFrames(frames) { 8 | return frames.split("\n").filter(function(frame) { return frame.indexOf(filename) < 0; }).join("\n"); 9 | } 10 | 11 | Error.prepareStackTrace = function(error, structuredStackTrace) { 12 | if (!error.__cachedTrace) { 13 | error.__cachedTrace = filterInternalFrames(FormatStackTrace(error, structuredStackTrace)); 14 | if (!has.call(error, "__previous")) { 15 | var previous = currentTraceError; 16 | while (previous) { 17 | var previousTrace = previous.stack; 18 | error.__cachedTrace += "\n----------------------------------------\n" + 19 | " at " + previous.__location + "\n" + 20 | previousTrace.substring(previousTrace.indexOf("\n") + 1); 21 | previous = previous.__previous; 22 | } 23 | } 24 | } 25 | return error.__cachedTrace; 26 | } 27 | 28 | var slice = Array.prototype.slice; 29 | var has = Object.prototype.hasOwnProperty; 30 | 31 | // Takes an object, a property name for the callback function to wrap, and an argument position 32 | // and overwrites the function with a wrapper that captures the stack at the time of callback registration 33 | function wrapRegistrationFunction(object, property, callbackArg) { 34 | if (typeof object[property] !== "function") { 35 | console.error("(long-stack-traces) Object", object, "does not contain function", property); 36 | return; 37 | } 38 | if (!has.call(object, property)) { 39 | console.warn("(long-stack-traces) Object", object, "does not directly contain function", property); 40 | } 41 | 42 | // TODO: better source position detection 43 | var sourcePosition = (object.constructor.name || Object.prototype.toString.call(object)) + "." + property; 44 | 45 | // capture the original registration function 46 | var fn = object[property]; 47 | // overwrite it with a wrapped registration function that modifies the supplied callback argument 48 | object[property] = function() { 49 | // replace the callback argument with a wrapped version that captured the current stack trace 50 | arguments[callbackArg] = makeWrappedCallback(arguments[callbackArg], sourcePosition); 51 | // call the original registration function with the modified arguments 52 | return fn.apply(this, arguments); 53 | } 54 | 55 | // check that the registration function was indeed overwritten 56 | if (object[property] === fn) 57 | console.warn("(long-stack-traces) Couldn't replace ", property, "on", object); 58 | } 59 | 60 | // Takes a callback function and name, and captures a stack trace, returning a new callback that restores the stack frame 61 | // This function adds a single function call overhead during callback registration vs. inlining it in wrapRegistationFunction 62 | function makeWrappedCallback(callback, frameLocation) { 63 | // add a fake stack frame. we can't get a real one since we aren't inside the original function 64 | var traceError = new Error(); 65 | traceError.__location = frameLocation; 66 | traceError.__previous = currentTraceError; 67 | return function() { 68 | // if (currentTraceError) { 69 | // FIXME: This shouldn't normally happen, but it often does. Do we actually need a stack instead? 70 | // console.warn("(long-stack-traces) Internal Error: currentTrace already set."); 71 | // } 72 | // restore the trace 73 | currentTraceError = traceError; 74 | try { 75 | return callback.apply(this, arguments); 76 | } catch (e) { 77 | var stack = e.stack; 78 | e.stack = stack; 79 | throw e; 80 | } finally { 81 | // clear the trace so we can check that none is set above. 82 | // TODO: could we remove this for slightly better performace? 83 | currentTraceError = null; 84 | } 85 | } 86 | } 87 | 88 | var global = (function() { return this; })(); 89 | wrapRegistrationFunction(global, "setTimeout", 0); 90 | wrapRegistrationFunction(global, "setInterval", 0); 91 | 92 | var EventEmitter = require('events').EventEmitter; 93 | //wrapRegistrationFunction(EventEmitter.prototype, "addListener", 1); 94 | //wrapRegistrationFunction(EventEmitter.prototype, "on", 1); 95 | 96 | wrapRegistrationFunction(process, "nextTick", 0); 97 | 98 | // Copyright 2006-2008 the V8 project authors. All rights reserved. 99 | // Redistribution and use in source and binary forms, with or without 100 | // modification, are permitted provided that the following conditions are 101 | // met: 102 | // 103 | // * Redistributions of source code must retain the above copyright 104 | // notice, this list of conditions and the following disclaimer. 105 | // * Redistributions in binary form must reproduce the above 106 | // copyright notice, this list of conditions and the following 107 | // disclaimer in the documentation and/or other materials provided 108 | // with the distribution. 109 | // * Neither the name of Google Inc. nor the names of its 110 | // contributors may be used to endorse or promote products derived 111 | // from this software without specific prior written permission. 112 | // 113 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 114 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 115 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 116 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 117 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 118 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 119 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 120 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 121 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 122 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 123 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 124 | 125 | function FormatStackTrace(error, frames) { 126 | var lines = []; 127 | try { 128 | lines.push(error.toString()); 129 | } catch (e) { 130 | try { 131 | lines.push(""); 132 | } catch (ee) { 133 | lines.push(""); 134 | } 135 | } 136 | for (var i = 0; i < frames.length; i++) { 137 | var frame = frames[i]; 138 | var line; 139 | try { 140 | line = FormatSourcePosition(frame); 141 | } catch (e) { 142 | try { 143 | line = ""; 144 | } catch (ee) { 145 | // Any code that reaches this point is seriously nasty! 146 | line = ""; 147 | } 148 | } 149 | lines.push(" at " + line); 150 | } 151 | return lines.join("\n"); 152 | } 153 | 154 | function FormatSourcePosition(frame) { 155 | var fileLocation = ""; 156 | if (frame.isNative()) { 157 | fileLocation = "native"; 158 | } else if (frame.isEval()) { 159 | fileLocation = "eval at " + frame.getEvalOrigin(); 160 | } else { 161 | var fileName = frame.getFileName(); 162 | if (fileName) { 163 | fileLocation += fileName; 164 | var lineNumber = frame.getLineNumber(); 165 | if (lineNumber != null) { 166 | fileLocation += ":" + lineNumber; 167 | var columnNumber = frame.getColumnNumber(); 168 | if (columnNumber) { 169 | fileLocation += ":" + columnNumber; 170 | } 171 | } 172 | } 173 | } 174 | if (!fileLocation) { 175 | fileLocation = "unknown source"; 176 | } 177 | var line = ""; 178 | var functionName = frame.getFunction().name; 179 | var addPrefix = true; 180 | var isConstructor = frame.isConstructor(); 181 | var isMethodCall = !(frame.isToplevel() || isConstructor); 182 | if (isMethodCall) { 183 | var methodName = frame.getMethodName(); 184 | line += frame.getTypeName() + "."; 185 | if (functionName) { 186 | line += functionName; 187 | if (methodName && (methodName != functionName)) { 188 | line += " [as " + methodName + "]"; 189 | } 190 | } else { 191 | line += methodName || ""; 192 | } 193 | } else if (isConstructor) { 194 | line += "new " + (functionName || ""); 195 | } else if (functionName) { 196 | line += functionName; 197 | } else { 198 | line += fileLocation; 199 | addPrefix = false; 200 | } 201 | if (addPrefix) { 202 | line += " (" + fileLocation + ")"; 203 | } 204 | return line; 205 | } 206 | })(typeof exports !== "undefined" ? exports : {}); 207 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | /* 19 | * lpad and rpad are taken from expresso 20 | * which is MIT licensed. 21 | * Copyright(c) TJ Holowaychuk 22 | */ 23 | 24 | var util = require('util'); 25 | var fs = require('fs'); 26 | var path = require('path'); 27 | var EventEmitter = require('events').EventEmitter; 28 | 29 | var sprintf = require('sprintf').sprintf; 30 | var async = require('async'); 31 | 32 | var constants = require('./constants'); 33 | var errors = require('./errors'); 34 | 35 | var printMsg = function(msg, verbosity, minVerbosity) { 36 | if (verbosity >= minVerbosity) { 37 | util.puts(msg); 38 | } 39 | }; 40 | 41 | var isNullOrUndefined = function(value) { 42 | if (value === null || ((typeof value === 'string') && value === 'null') || 43 | value === undefined || ((typeof value === 'string') && 44 | value === 'undefined')) { 45 | return true; 46 | } 47 | 48 | return false; 49 | }; 50 | 51 | /** 52 | * Call a function and ignore any error thrown. 53 | * 54 | * @param {Function} func Function to call. 55 | * @param {Object} context Context in which the function is called. 56 | * @param {Array} Function argument 57 | * @param {Function} callback Optional callback which is called at the end. 58 | */ 59 | var callIgnoringError = function(func, context, args, callback) { 60 | try { 61 | func.apply(context, args); 62 | } 63 | catch (err) {} 64 | 65 | if (callback) { 66 | callback(); 67 | } 68 | }; 69 | 70 | /** 71 | * Return Unix timestamp. 72 | * 73 | * @return {Number} Unix timestamp. 74 | */ 75 | var getUnixTimestamp = function() { 76 | return (new Date().getTime() / 1000); 77 | }; 78 | 79 | var addCharacters = function(string, width, character) { 80 | var width_ = width || 80; 81 | var character_ = character || ' '; 82 | var stringLen = string.length; 83 | var left = (width_ - stringLen); 84 | 85 | if (left <= 0) { 86 | return string; 87 | } 88 | 89 | while (left--) { 90 | string += character_; 91 | } 92 | 93 | return string; 94 | }; 95 | 96 | /** 97 | * Pad the given string to the maximum width provided. 98 | * 99 | * @param {String} str 100 | * @param {Number} width 101 | * @return {String} 102 | */ 103 | function lpad(str, width) { 104 | str = String(str); 105 | var n = width - str.length; 106 | 107 | if (n < 1) { 108 | return str; 109 | } 110 | 111 | while (n--) { 112 | str = ' ' + str; 113 | } 114 | 115 | return str; 116 | } 117 | 118 | /** 119 | * Pad the given string to the maximum width provided. 120 | * 121 | * @param {String} str 122 | * @param {Number} width 123 | * @return {String} 124 | */ 125 | function rpad(str, width) { 126 | str = String(str); 127 | var n = width - str.length; 128 | 129 | if (n < 1) { 130 | return str; 131 | } 132 | 133 | while (n--) { 134 | str = str + ' '; 135 | } 136 | 137 | return str; 138 | } 139 | 140 | function LineProcessor(initialData) { 141 | this._buffer = initialData || ''; 142 | } 143 | 144 | util.inherits(LineProcessor, EventEmitter); 145 | 146 | LineProcessor.prototype.appendData = function(data) { 147 | this._buffer += data; 148 | this._processData(); 149 | }; 150 | 151 | LineProcessor.prototype._processData = function() { 152 | var newLineMarkerIndex, line; 153 | 154 | newLineMarkerIndex = this._buffer.indexOf('\n'); 155 | while (newLineMarkerIndex !== -1) { 156 | line = this._buffer.substring(0, newLineMarkerIndex); 157 | this._buffer = this._buffer.substring(newLineMarkerIndex + 1); 158 | newLineMarkerIndex = this._buffer.indexOf('\n'); 159 | 160 | this.emit('line', line); 161 | } 162 | }; 163 | 164 | function parseResultLine(line) { 165 | // Returns a triple (end, fileName, resultObj) 166 | var startMarkerIndex, endMarkerIndex, testFileEndMarkerIndex; 167 | var coverageEndMarker, split, fileName, resultObj; 168 | 169 | testFileEndMarkerIndex = line.indexOf(constants.TEST_FILE_END_MARKER); 170 | coverageEndMarker = line.indexOf(constants.COVERAGE_END_MARKER); 171 | startMarkerIndex = line.indexOf(constants.TEST_START_MARKER); 172 | endMarkerIndex = line.indexOf(constants.TEST_END_MARKER); 173 | 174 | if (testFileEndMarkerIndex !== -1) { 175 | // end marker 176 | fileName = line.substring(0, testFileEndMarkerIndex); 177 | return [ true, fileName, null ]; 178 | } 179 | else if (coverageEndMarker !== -1) { 180 | // coverage result 181 | split = line.split(constants.DELIMITER); 182 | resultObj = { 183 | 'coverage': split[1].replace(constants.COVERAGE_END_MARKER, '') 184 | }; 185 | 186 | return [ false, split[0], resultObj ]; 187 | } 188 | else { 189 | // test result 190 | line = line.replace(constants.TEST_START_MARKER, '') 191 | .replace(constants.TEST_END_MARKER, ''); 192 | split = line.split(constants.DELIMITER); 193 | resultObj = JSON.parse(split[1]); 194 | 195 | return [ null, split[0], resultObj ]; 196 | } 197 | } 198 | 199 | function isTestFile(filePath) { 200 | var exportedValues, key, value; 201 | 202 | try { 203 | exportedValues = require(filePath); 204 | } 205 | catch (err) { 206 | return false; 207 | } 208 | 209 | for (key in exportedValues) { 210 | if (exportedValues.hasOwnProperty(key)) { 211 | value = exportedValues[key]; 212 | if (key.indexOf('test') === 0 && (typeof value === 'function')) { 213 | return true; 214 | } 215 | } 216 | } 217 | 218 | return false; 219 | } 220 | 221 | /** 222 | * Get files in a directory which match the provided name pattern. 223 | * Note: This function recurses into sub-directories. 224 | * 225 | * @param {String} directory Directory to search. 226 | * @param {String} matchPattern File name match pattern. 227 | * @param {Object} options Optional options object. 228 | * @param {Function} callback Callback called with (err, matchingFilePaths). 229 | */ 230 | function getMatchingFiles(directory, matchPattern, options, callback) { 231 | options = options || {}; 232 | var matchedFiles = [], 233 | recurse = options.recurse || false; 234 | 235 | fs.readdir(directory, function(err, files) { 236 | if (err) { 237 | callback(null, matchedFiles); 238 | return; 239 | } 240 | 241 | async.forEach(files, function(file, callback) { 242 | var filePath = path.join(directory, file); 243 | fs.stat(filePath, function(err, stats) { 244 | if (err) { 245 | callback(); 246 | } 247 | else if (stats.isDirectory() && recurse) { 248 | getMatchingFiles(filePath, matchPattern, options, callback); 249 | } 250 | else if (matchPattern.test(file)) { 251 | matchedFiles.push(filePath); 252 | callback(); 253 | } 254 | else { 255 | callback(); 256 | } 257 | }); 258 | }, 259 | 260 | function(err) { 261 | callback(err, matchedFiles); 262 | }); 263 | }); 264 | } 265 | 266 | function findTestFiles(basePath, callback) { 267 | getMatchingFiles(basePath, /\.js/, {'recurse': true}, callback); 268 | } 269 | 270 | function parseArguments(rules, args) { 271 | var key, value, rule; 272 | var result = {}; 273 | 274 | for (key in rules) { 275 | if (rules.hasOwnProperty(key)) { 276 | rule = rules[key]; 277 | value = args[rule['pos']]; 278 | 279 | if (isNullOrUndefined(value)) { 280 | value = null; 281 | } 282 | else if (rule['type'] === 'number') { 283 | value = parseInt(value, 10); 284 | } 285 | 286 | result[key] = value; 287 | } 288 | } 289 | 290 | return result; 291 | } 292 | 293 | /** 294 | * Wrap a function so that the original function will only be called once, 295 | * regardless of how many times the wrapper is called. 296 | * @param {Function} fn The to wrap. 297 | * @return {Function} A function which will call fn the first time it is called. 298 | */ 299 | function fireOnce(fn) { 300 | var fired = false; 301 | return function wrapped() { 302 | if (!fired) { 303 | fired = true; 304 | fn.apply(null, arguments); 305 | } 306 | }; 307 | } 308 | 309 | /** 310 | * Very simple object merging. 311 | * Merges two objects together, returning a new object containing a 312 | * superset of all attributes. Attributes in b are prefered if both 313 | * objects have identical keys. 314 | * 315 | * @param {Object} a Object to merge. 316 | * @param {Object} b Object to merge, wins on conflict. 317 | * @return {Object} The merged object. 318 | */ 319 | function merge(a, b) { 320 | var c = {}; 321 | var attrname; 322 | for (attrname in a) { 323 | if (a.hasOwnProperty(attrname)) { 324 | c[attrname] = a[attrname]; 325 | } 326 | } 327 | for (attrname in b) { 328 | if (b.hasOwnProperty(attrname)) { 329 | c[attrname] = b[attrname]; 330 | } 331 | } 332 | return c; 333 | } 334 | 335 | exports.printMsg = printMsg; 336 | exports.isNullOrUndefined = isNullOrUndefined; 337 | 338 | exports.getUnixTimestamp = getUnixTimestamp; 339 | exports.addCharacters = addCharacters; 340 | 341 | exports.rpad = rpad; 342 | exports.lpad = lpad; 343 | 344 | exports.LineProcessor = LineProcessor; 345 | exports.parseResultLine = parseResultLine; 346 | exports.getMatchingFiles = getMatchingFiles; 347 | exports.findTestFiles = findTestFiles; 348 | exports.parseArguments = parseArguments; 349 | exports.fireOnce = fireOnce; 350 | exports.merge = merge; 351 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Whiskey 2 | 3 | Whiskey is a powerful test runner for Node.js applications and a process 4 | orchestration framework which makes running integration tests with a lot of 5 | service / process dependencies easier. 6 | 7 | ## Features 8 | 9 | * Each test file runs isolated in a separate process 10 | * Support for running multiple tests in parallel in a single suite (`--concurrency` option) 11 | * Support for running multiple suites in parallel (`--independent-tests` option) 12 | * Support for a test initialization function which is run before running the tests in a test file 13 | * Support for a test file timeout 14 | * Per-test `setUp` / `tearDown` function support 15 | * Per-suite (test file) `initialize` / `finalize` function support 16 | * Per-session, or global, setUp / tearDown function support 17 | * Support for different test reporters (cli, tap) 18 | * Support for code coverage (cli reporter, html reporter) 19 | * Support for reporting variables which have leaked into a global scope 20 | * Nicely formatted reports (colors!) 21 | * Integration with node debugger 22 | * Support for generating Makefiles with different Whiskey targets 23 | 24 | ## Changes 25 | 26 | For changes please see [CHANGES.md](https://github.com/cloudkick/whiskey/blob/master/CHANGES.md) file. 27 | 28 | ## Installation 29 | 30 | Install it using npm: 31 | 32 | ``` 33 | npm install whiskey 34 | ``` 35 | 36 | ## Usage 37 | 38 | whiskey [options] --tests "" 39 | 40 | whiskey [options] --independent-tests "" 41 | 42 | whiskey [options] --tests "" --independent-tests "" 43 | 44 | 45 | ### Available options 46 | 47 | * **-t, --tests** - Whitespace separated list of test suites to run sequentially 48 | * **-T, --independent-tests** - Whitespace separated list of test suites to run concurrently 49 | * **-m, --max-suites NUMBER** - The number of concurrently executing independent test suites (defaults to 5) 50 | * **-ti, --test-init-file** - A path to the initialization file which must export 51 | `init` function and it is called in a child process *before running the tests in 52 | each test file 53 | * **-c, --chdir** - An optional path to which the child process will chdir to before 54 | running the tests 55 | * **-g, --global-setup-teardown STRING** - Specifies the file containing the globalSetUp and globalTearDown procedures. 56 | * **--timeout [NUMBER]** - How long to wait for tests to complete before timing 57 | out 58 | * **--failfast** - Stop running the tests on a first failure or a timeout 59 | * **--no-styles** - Don't use styles and colors 60 | * **--concurrency [NUMBER]** - Maximum number of tests which will run in parallel (defaults to 1) 61 | * **--quiet** - Don't print stdout and stderr 62 | * **--real-time** - Print stdout and stderr as soon as it comes in 63 | * **--test-reporter [cli,tap]** - Which test reporter to use (defaults to cli) 64 | * **--coverage** - Use this option to enable the test coverage 65 | * **--coverage-reporter [cli,html]** - Which coverage reporter to use (defaults to cli) 66 | * **--coverage-dir** - Directory where the coverage HTML report is saved 67 | * **--scope-leaks** - Record which variables were leaked into a global scope 68 | * **--scope-leaks-reporter [cli]** - Which scope leak reporter to use (defaults 69 | to cli) 70 | * **--debug NUMBER** - Attach a debugger to a test process listening on the specified port number 71 | * **--report-timing** - Report each test run time 72 | * **--dependencies STRING** - Specify path to the dependencies file for the 73 | process runner. More information about the process runner can be found at 74 | [PROCESS_RUNNER.md](https://github.com/cloudkick/whiskey/blob/master/PROCESS_RUNNER.md) 75 | * **--only-essential-dependencies** - Only start dependencies required by the tests 76 | files which are ran. This option is only applicable if `--dependencies` option 77 | is used. 78 | 79 | Note: When specifying multiple test a list with the test paths must be quoted, 80 | for example: `whiskey --tests "tests/a.js tests/b.js tests/c.js"` 81 | 82 | ## A Note about setUp and tearDown 83 | 84 | Presently, two kinds of setup and teardown procedures exist with Whiskey. 85 | setUp and tearDown work on a per-test basis; that is, Whiskey invokes setUp 86 | before running a test in a given Javascript file, called a suite and tearDown 87 | is invoked after a test run has finished. 88 | If you run multiple suites in parallel (e.g., via the 89 | -T/--independent-tests option), you'll get concurrent execution of setups and 90 | teardowns as well. 91 | 92 | Sometimes, though, you need longer-lived environmental configurations, or you 93 | need safe resource sharing between entire batches of independently running 94 | tests. For these, you'll want to use globalSetUp and globalTearDown. 95 | 96 | * When do I use setUp / tearDown? 97 | * When a suite's runtime environment **does not** influence other running suites. 98 | * When do I use globalSetUp / globalTearDown ? 99 | * When a suite's runtime environment **can potentially** interfere with other, concurrently running suites. 100 | * **Example:** Attempting to run multiple suites in parallel which rely on a Cassandra schema being in place, and each attempting to reset the schema to a known state on a single Cassandra instance, you'll get Cassandra schema version errors. Using globalSetUp prevents this by running the schema reset code exactly once for _all_ tests. 101 | 102 | ## Test File Examples 103 | 104 | A simple example (success): 105 | 106 | ```javascript 107 | var called = 0; 108 | 109 | exports.test_async_one_equals_one = function(test, assert) { 110 | setTimeout(function() { 111 | assert.equal(1, 1); 112 | called++; 113 | test.finish(); 114 | }, 1000); 115 | }; 116 | 117 | exports.tearDown = function(test, assert) { 118 | assert.equal(called, 1); 119 | test.finish(); 120 | }; 121 | ``` 122 | 123 | A simple example (skipping a test): 124 | 125 | ```javascript 126 | var dbUp = false; 127 | 128 | exports.test_query = function(test, assert) { 129 | if (!dbUp) { 130 | test.skip('Database is not up, skipping...'); 131 | return; 132 | } 133 | 134 | assert.equal(2, 1); 135 | test.finish(); 136 | }; 137 | ``` 138 | 139 | A simple example (failure): 140 | 141 | ```javascript 142 | exports.test_two_equals_one = function(test, assert) { 143 | assert.equal(2, 1); 144 | test.finish(); 145 | }; 146 | ``` 147 | 148 | A simple example using the optional BDD module: 149 | ```javascript 150 | var bdd = require('whiskey').bdd.init(exports); 151 | var describe = bdd.describe; 152 | 153 | describe('the bdd module', function(it) { 154 | it('supports it(), expect(), and toEqual()', function(expect) { 155 | expect(true).toEqual(true); 156 | }); 157 | }); 158 | ``` 159 | 160 | A simple example demonstrating how to use global setup and teardown functionality: 161 | ``` javascript 162 | exports['globalSetUp'] = function(test, assert) { 163 | // Set up database schema here... 164 | // Push known data set to database here... 165 | test.finish(); 166 | } 167 | 168 | exports['globalTearDown'] = function(test, assert) { 169 | // Drop database here... 170 | test.finish(); 171 | } 172 | ``` 173 | 174 | For more examples please check the `example/` folder, and the `test/run.sh` script. 175 | 176 | ## Build status 177 | 178 | [![Build Status](https://secure.travis-ci.org/cloudkick/whiskey.png)](http://travis-ci.org/cloudkick/whiskey) 179 | 180 | ## Running Whiskey test suite 181 | 182 | To run the Whiskey test suite, run the following command in the repository root 183 | directory. 184 | 185 | ```bash 186 | npm test 187 | ``` 188 | 189 | If all the tests have sucessfully passed, the process should exit with a zero 190 | status code and you should see `* * * Whiskey test suite PASSED. * * *` 191 | message. 192 | 193 | ## Contributing 194 | 195 | To contribute, fork the repository, create a branch with your changes and open a 196 | pull request. 197 | 198 | ## Debugging 199 | 200 | If you want to debug your test, you can use the `--debug` option. This will 201 | cause Whiskey to start the test process with the V8 debugger functionality. 202 | You then need to manually connect to the debugger to control it (i.e. using 203 | node repl or [node-inspector](https://github.com/dannycoates/node-inspector)). 204 | 205 | Whiskey will also by default set a breakpoint at the beginning of your test 206 | file. 207 | 208 | Note: This option can only be used with a single test file. Further, you 209 | cannot use the `--debug` and `--independent-tests` options together. The 210 | semantics just don't make any sense. To debug a test, make sure you invoke it 211 | with `--tests` instead. 212 | 213 | ## Troubleshooting 214 | 215 | ### I use `long-stack-straces` module in my own code and all of the tests get reported as succeeded 216 | 217 | Long stack traces modules intercepts the default Error object and throws a custom 218 | one. The problem with this is that Whiskey internally relies on attaching the 219 | test name to the `Error` object so it can figure out to which test the exception 220 | belongs. long-stack-traces throws a custom Error object and as a consequence test 221 | name attribute gets lost so Whiskey thinks your test didn't throw any exceptions. 222 | 223 | The solution for this problem is to disable `long-stack-trace` module when running 224 | the tests. This shouldn't be a big deal, because Whiskey internally already uses 225 | `long-stack-traces` module which means that you will still get long stack traces 226 | in the exceptions which were thrown in your tests. 227 | 228 | ### My test gets reported as "timeout" instead of "failure" 229 | 230 | If your test gets reported as "timeout" instead of "failure" your test code most 231 | likely looks similar to the one below: 232 | 233 | ```javascript 234 | exports.test_failure = function(test, assert){ 235 | setTimeout(function() { 236 | throw "blaaaaah"; 237 | test.finish(); 238 | },200); 239 | }; 240 | ``` 241 | 242 | The problem with this is that if you run tests in parallel (`--concurrency` > 1) 243 | and you don't use a custom assert object which gets passed to each test function, 244 | Whiskey can't figure out to which test the exception belongs. As a consequence, 245 | the test is reported as "timed out" and the exception is reported as "uncaught". 246 | 247 | The solution for this problem is to run the tests in sequential mode (drop the 248 | --concurrency option). 249 | 250 | ## License 251 | 252 | Apache 2.0, for more info see [LICENSE](/cloudkick/whiskey/blob/master/LICENSE). 253 | -------------------------------------------------------------------------------- /lib/assert.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | * Many of the modifications to 'assert' that take place here are borrowed 18 | * from the 'Expresso' test framework: 19 | * 20 | * 21 | * Expresso 22 | * Copyright(c) TJ Holowaychuk 23 | * (MIT Licensed) 24 | */ 25 | 26 | var util = require('util'); 27 | var url = require('url'); 28 | var http = require('http'); 29 | var https = require('https'); 30 | 31 | var port = parseInt((Math.random() * (65500 - 2000) + 2000), 10); 32 | 33 | // Code bellow is taken from Node core 34 | // http://wiki.commonjs.org/wiki/Unit_Testing/1.0 35 | // 36 | // THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8! 37 | // 38 | // Originally from narwhal.js (http://narwhaljs.org) 39 | // Copyright (c) 2009 Thomas Robinson <280north.com> 40 | // 41 | // Permission is hereby granted, free of charge, to any person obtaining a copy 42 | // of this software and associated documentation files (the 'Software'), to 43 | // deal in the Software without restriction, including without limitation the 44 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 45 | // sell copies of the Software, and to permit persons to whom the Software is 46 | // furnished to do so, subject to the following conditions: 47 | // 48 | // The above copyright notice and this permission notice shall be included in 49 | // all copies or substantial portions of the Software. 50 | // 51 | // THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 52 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 53 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 54 | // AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 55 | // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 56 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 57 | 58 | var origAssert = require('assert'); 59 | 60 | var assert = {}; 61 | 62 | /* 63 | * Alias deepEqual as eql for complex equality 64 | */ 65 | assert.eql = origAssert.deepEqual; 66 | 67 | /** 68 | * Assert that `val` is null. 69 | * 70 | * @param {Mixed} val 71 | * @param {String} msg 72 | */ 73 | assert.isNull = function(val, msg) { 74 | assert.strictEqual(null, val, msg); 75 | }; 76 | 77 | assert.ifError = function(err) { 78 | if (err) { 79 | if (err instanceof Error) { 80 | Error.captureStackTrace(err, arguments.callee); 81 | } 82 | 83 | throw err; 84 | } 85 | }; 86 | 87 | /** 88 | * Assert that `val` is not null. 89 | * 90 | * @param {Mixed} val 91 | * @param {String} msg 92 | */ 93 | assert.isNotNull = function(val, msg) { 94 | assert.notStrictEqual(null, val, msg); 95 | }; 96 | 97 | /** 98 | * Assert that `val` is undefined. 99 | * 100 | * @param {Mixed} val 101 | * @param {String} msg 102 | */ 103 | assert.isUndefined = function(val, msg) { 104 | assert.strictEqual(undefined, val, msg); 105 | }; 106 | 107 | /** 108 | * Assert that `val` is not undefined. 109 | * 110 | * @param {Mixed} val 111 | * @param {String} msg 112 | */ 113 | assert.isDefined = function(val, msg) { 114 | assert.notStrictEqual(undefined, val, msg); 115 | }; 116 | 117 | /** 118 | * Assert that `obj` is `type`. 119 | * 120 | * @param {Mixed} obj 121 | * @param {String} type 122 | * @api public 123 | */ 124 | assert.type = function(obj, type, msg){ 125 | var real = typeof obj; 126 | msg = msg || 'typeof ' + util.inspect(obj) + ' is ' + real + ', expected ' + type; 127 | assert.ok(type === real, msg); 128 | }; 129 | 130 | /** 131 | * Assert that `str` matches `regexp`. 132 | * 133 | * @param {String} str 134 | * @param {RegExp} regexp 135 | * @param {String} msg 136 | */ 137 | assert.match = function(str, regexp, msg) { 138 | msg = msg || util.inspect(str) + ' does not match ' + util.inspect(regexp); 139 | assert.ok(regexp.test(str), msg); 140 | }; 141 | 142 | /** 143 | * Assert that `val` is within `obj`. 144 | * 145 | * Examples: 146 | * 147 | * assert.includes('foobar', 'bar'); 148 | * assert.includes(['foo', 'bar'], 'foo'); 149 | * 150 | * @param {String|Array} obj 151 | * @param {Mixed} val 152 | * @param {String} msg 153 | */ 154 | assert.includes = function(obj, val, msg) { 155 | msg = msg || util.inspect(obj) + ' does not include ' + util.inspect(val); 156 | assert.ok(obj.indexOf(val) >= 0, msg); 157 | }; 158 | 159 | /** 160 | * Assert length of `val` is `n`. 161 | * 162 | * @param {Mixed} val 163 | * @param {Number} n 164 | * @param {String} msg 165 | */ 166 | assert.length = function(val, n, msg) { 167 | msg = msg || util.inspect(val) + ' has length of ' + val.length + ', expected ' + n; 168 | assert.equal(n, val.length, msg); 169 | }; 170 | 171 | /** 172 | * Assert response from `server` with 173 | * the given `req` object and `res` assertions object. 174 | * 175 | * @param {Server} server 176 | * @param {Object} req 177 | * @param {Object|Function} res 178 | * @param {String} msg 179 | */ 180 | assert.response = function(server, req, res, msg){ 181 | // Callback as third or fourth arg 182 | var callback = typeof res === 'function' ? res : typeof msg === 'function' ? msg : function(){}; 183 | 184 | // Default messate to test title 185 | if (typeof msg === 'function') { 186 | msg = null; 187 | } 188 | 189 | msg = msg || assert.testTitle; 190 | msg += '. '; 191 | 192 | // Pending responses 193 | server.__pending = server.__pending || 0; 194 | server.__pending++; 195 | 196 | server.listen(server.__port = port++, '127.0.0.1'); 197 | 198 | process.nextTick(function() { 199 | // Issue request 200 | var timer; 201 | var trailer; 202 | var method = req.method || 'GET'; 203 | var status = res.status || res.statusCode; 204 | var data = req.data || req.body; 205 | var streamer = req.streamer; 206 | var timeout = req.timeout || 0; 207 | var headers = req.headers || {}; 208 | 209 | for (trailer in req.trailers) { 210 | if (req.trailers.hasOwnProperty(trailer)) { 211 | if (headers['Trailer']) { 212 | headers['Trailer'] += ', ' + trailer; 213 | } 214 | else { 215 | headers['Trailer'] = trailer; 216 | } 217 | } 218 | } 219 | 220 | var urlParsed = url.parse(req.url); 221 | var reqOptions = { 222 | 'host': '127.0.0.1', 223 | 'port': server.__port, 224 | 'path': urlParsed.pathname, 225 | 'method': method, 226 | 'headers': headers 227 | }; 228 | 229 | var reqMethod = (urlParsed.protocol === 'http:' || !urlParsed.hasOwnProperty('protocol')) ? http.request : https.request; 230 | var request = http.request(reqOptions); 231 | 232 | if (req.trailers) { 233 | request.addTrailers(req.trailers); 234 | } 235 | 236 | // Timeout 237 | if (timeout) { 238 | timer = setTimeout(function() { 239 | server.__pending--; 240 | 241 | if (!server.__pending) { 242 | server.close(); 243 | } 244 | 245 | delete req.timeout; 246 | assert.fail(msg + 'Request timed out after ' + timeout + 'ms.'); 247 | }, timeout); 248 | } 249 | 250 | if (data) { 251 | request.write(data); 252 | } 253 | 254 | request.addListener('response', function(response) { 255 | response.body = ''; 256 | response.setEncoding('utf8'); 257 | response.addListener('data', function(chunk){ response.body += chunk; }); 258 | response.addListener('end', function(){ 259 | server.__pending--; 260 | 261 | if (!server.__pending) { 262 | server.close(); 263 | } 264 | 265 | if (timer) { 266 | clearTimeout(timer); 267 | } 268 | 269 | // Assert response body 270 | if (res.body !== undefined) { 271 | assert.equal( 272 | response.body, 273 | res.body, 274 | msg + 'Invalid response body.\n' + 275 | ' Expected: ' + util.inspect(res.body) + '\n' + 276 | ' Got: ' + util.inspect(response.body) 277 | ); 278 | } 279 | 280 | // Assert response status 281 | if (typeof status === 'number') { 282 | assert.equal( 283 | response.statusCode, 284 | status, 285 | msg + 'Invalid response status code.\n' + 286 | ' Expected: [{' + status + '}\n' + 287 | ' Got: {' + response.sttusCode + '}' 288 | ); 289 | } 290 | 291 | // Assert response headers 292 | if (res.headers) { 293 | var keys = Object.keys(res.headers); 294 | for (var i = 0, len = keys.length; i < len; ++i) { 295 | var name = keys[i]; 296 | var actual = response.headers[name.toLowerCase()]; 297 | var expected = res.headers[name]; 298 | assert.equal( 299 | actual, 300 | expected, 301 | msg + 'Invalid response header [bold]{' + name + '}.\n' + 302 | ' Expected: {' + expected + '}\n' + 303 | ' Got: {' + actual + '}' 304 | ); 305 | } 306 | } 307 | 308 | callback(response); 309 | }); 310 | }); 311 | 312 | if (streamer) { 313 | streamer(request); 314 | } 315 | else { 316 | request.end(); 317 | } 318 | }); 319 | }; 320 | 321 | function merge(obj1, obj2, ignore) { 322 | obj1 = obj1 || assert; 323 | ignore = ignore || []; 324 | 325 | for (var key in obj2) { 326 | if (obj2.hasOwnProperty(key) && ignore.indexOf(key) === -1) { 327 | obj1[key] = obj2[key]; 328 | } 329 | } 330 | } 331 | 332 | function getAssertModule(test) { 333 | assert.AssertionError = function AssertionError(options) { 334 | origAssert.AssertionError.call(this, options); 335 | this.test = test; 336 | }; 337 | 338 | util.inherits(assert.AssertionError, origAssert.AssertionError); 339 | 340 | var ignore = ['AssertionError']; 341 | for (var key in origAssert) { 342 | if (origAssert.hasOwnProperty(key) && ignore.indexOf(key) === -1) { 343 | assert[key] = (function(key) { 344 | return function() { 345 | try { 346 | origAssert[key].apply(null, arguments); 347 | } 348 | catch (err) { 349 | err.test = test; 350 | throw err; 351 | } 352 | }; 353 | })(key); 354 | } 355 | } 356 | 357 | return assert; 358 | } 359 | 360 | exports.getAssertModule = getAssertModule; 361 | exports.merge = merge; 362 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | 204 | --------------------------------------------------------------------------------