├── .gitignore ├── .travis.yml ├── README.md ├── bin └── specify ├── package.json ├── reporters ├── compact.js ├── default.js └── json.js ├── specify.js ├── test.sh └── test ├── fixtures ├── all.txt ├── default_reporter.txt ├── filters.txt ├── reporter.txt └── single.txt ├── specify.js └── specify ├── compact.js ├── parallel.js └── singletest.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | *.log -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: "node_js" 2 | node_js: 3 | - 0.8 4 | - 0.9 5 | branches: 6 | only: 7 | - master -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # specify 3 | 4 | `specify` is the simplest way i could think to do node.js testing. 5 | 6 | It works with sync code and async code all the same. 7 | 8 | Please use versions `~0.6.x` for node `0.6` and `~1.0.x` for node `0.8` or higher. 9 | 10 | If you don't like reading and want to see some code you can look at [nano's tests](https://github.com/dscape/nano/tree/master/tests) and learn there. Also read the `specify` source code; it's just a couple of lines of code. 11 | 12 | ``` js 13 | var specify = require('specify'); 14 | 15 | specify('create_by_secret', function (assert) { 16 | user.create_by_secret({invitation_code: "1234321!!"}, function (err) { 17 | assert.equal(err.eid, "ec:api:user:create_by_secret:wrong_code"); 18 | assert.equal(err.status_code, 400); 19 | }); 20 | }); 21 | 22 | specify.run(); 23 | ``` 24 | 25 | The assert calls are callback functions that wrap the assert module. `specify` figures out how many callbacks you are calling by [static-analysis]. To put it simply, it counts the number of times you wrote `assert`. When that number of assertions is met, or when a timeout occurs, that test is complete and we can execute the next one. 26 | 27 | Static analysis does not work for a `for` loop and some other flow control constructs. In that case you can use `assert.expect(nr)` to tell specify how many assertions to expect: 28 | 29 | ``` js 30 | specify('more_assertions_than_asserts', function(assert) { 31 | assert.expect(5); 32 | for(var i in [1,2,3,4,5]) { 33 | assert.equal(i,i); 34 | } 35 | }); 36 | ``` 37 | 38 | `specify` runs tests one by one, not in parallel. If you set `assert.expect` higher than the number of `assert` function calls the execution will stop, and your current test will never finish. You can circumvent this by setting a timeout: 39 | 40 | ``` js 41 | specify('foo', 50, function (assert) { 42 | call_to_db_that_takes_a_long_time(function (data) { 43 | assert.equal(data, 'foo'); 44 | }); 45 | }); 46 | ``` 47 | 48 | Because tests are serialized, `specify` can catch uncaught exceptions and continue to run. You will get a report about the error that was thrown somewhere in your stack. This is analogous to the functionality the community refers to as `domains`. 49 | 50 | `specify` is standalone; you don't need any special binaries to run it. 51 | 52 | If you think all these `specify` functions make your code look bloated, you can also run a single function: 53 | 54 | ``` js 55 | var specify = require('specify') 56 | , request = require('request') 57 | ; 58 | 59 | specify.run( 60 | 61 | function (assert) { 62 | 63 | var get = { uri: "http://someservice.com/1/apps/dscape", json: true } 64 | , del = { uri: "http://someservice.com/1/apps/dscape", method: "DELETE" 65 | , json : true } 66 | , app_name 67 | ; 68 | 69 | request(get, function (err, _, body) { 70 | 71 | assert.equal(err,null); 72 | assert.ok(body.rows); 73 | assert.ok(body.total_rows >= 1); 74 | assert.ok(body.rows.length >= 1); 75 | 76 | app_name = body.rows[0].value.app_name; 77 | del.uri += "/" + app_name; 78 | 79 | request(del, function (err, resp, body) { 80 | 81 | assert.equal(err,null); 82 | assert.equal(resp.statusCode, 200); 83 | assert.equal(body.app.name, app_name); 84 | assert.equal(body.app.user,"dscape"); 85 | 86 | }); 87 | 88 | }); 89 | 90 | } 91 | 92 | ); 93 | ``` 94 | 95 | 96 | # installation 97 | 98 | 99 | ## node.js 100 | 101 | 1. install [npm] 102 | 2. `npm install specify` 103 | 3. `var specify = require('specify');` 104 | 105 | 106 | # filtering 107 | 108 | In `specify` you specify which tests you want to run: 109 | 110 | ``` js 111 | var specify = require('specify') 112 | , filters = process.argv.slice(2) 113 | ; 114 | 115 | specify('foo', function (assert) { 116 | assert.equal('foo', 'foo'); 117 | }); 118 | 119 | specify('bar', function (assert) { 120 | assert.equal('bar', 'baz', 'bar failed'); 121 | }); 122 | 123 | specify('baz', function (assert) { 124 | assert.equal('baz', 'baz'); 125 | }); 126 | 127 | specify.run(filters); 128 | ``` 129 | 130 | 131 | # reporters 132 | 133 | If you feel like the output sent to `stdout` is ugly, you can write your own reporter and send in a pull request. 134 | 135 | Now use it: 136 | 137 | ``` js 138 | specify('specify#ask_for_a_specific_reporter', function(assert) { 139 | specify.reporter('my_awesome_reporter'); 140 | setTimeout(function (){ 141 | assert.ok(true); 142 | },1); 143 | }); 144 | ``` 145 | 146 | You can also do this with a function if you like: 147 | 148 | ``` js 149 | specify('specify#custom_reporter_from_function', function(assert) { 150 | specify.reporter(function (name, report, errors) { 151 | console.log(name); 152 | }); 153 | setTimeout(function () { 154 | assert.ok(false, 'i see dead people'); 155 | assert.ok(true); 156 | },1); 157 | }); 158 | ``` 159 | 160 | 161 | # samples 162 | 163 | Samples are available in the `/test` folder. 164 | 165 | 166 | # contribute 167 | 168 | Everyone is welcome to contribute. Patches, bug-fixes, reporters, new features. 169 | 170 | 1. Create an [issue][issues] so the community can comment on your idea 171 | 2. Fork `specify` 172 | 3. Create a new branch `git checkout -b feature_name` 173 | 4. Create tests for the changes you made 174 | 5. Make sure you pass both existing and newly inserted tests 175 | 6. Commit your changes 176 | 7. Push to your branch `git push origin feature_name` 177 | 8. Create a pull request 178 | 179 | 180 | # meta 181 | 182 | * Code: `git clone git://github.com/dscape/specify.git` 183 | * Home: 184 | * Bugs: 185 | * Build: [![build status](https://secure.travis-ci.org/dscape/specify.png)](http://travis-ci.org/dscape/specify) 186 | 187 | 188 | `(oO)--',-` in [caos] 189 | 190 | 191 | # license 192 | 193 | Copyright 2012 nuno job `(oO)--',--` 194 | 195 | Licensed under the apache license, version 2.0 (the "license"); 196 | You may not use this file except in compliance with the license. 197 | You may obtain a copy of the license at 198 | 199 | http://www.apache.org/licenses/LICENSE-2.0 200 | 201 | Unless required by applicable law or agreed to in writing, software 202 | distributed under the license is distributed on an "as is" basis, 203 | without warranties or conditions of any kind, either express or implied. 204 | see the license for the specific language governing permissions and 205 | limitations under the license. 206 | 207 | [npm]: http://npmjs.org 208 | [issues]: http://github.com/dscape/specify/issues 209 | [caos]: http://caos.di.uminho.pt/ 210 | [static-analysis]: http://en.wikipedia.org/wiki/Static_program_analysis 211 | -------------------------------------------------------------------------------- /bin/specify: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var fs = require('fs') 3 | , path = require('path') 4 | , tty = require('tty') 5 | , spawn = require('child_process').spawn 6 | , argv = require('optimist').argv 7 | , stack = [] 8 | , success = true 9 | , args = {env: process.env} 10 | , counts = {ok: 0, total: 0} 11 | , buffer = '' 12 | , base_path 13 | , dirs 14 | , files 15 | , test 16 | ; 17 | 18 | // 19 | // Will try to find tests in `tests` and `test` 20 | // Throws otherwise 21 | // 22 | try { 23 | base_path = path.resolve('.', 'tests'); 24 | dirs = fs.readdirSync(base_path); 25 | } catch (exc) { 26 | base_path = path.resolve('.', 'test'); 27 | dirs = fs.readdirSync(base_path); 28 | } 29 | 30 | if (argv._.length > 0) { 31 | dirs = argv._; 32 | } 33 | 34 | // 35 | // Set env vars in a platform agnostic way 36 | // 37 | (Array.isArray(argv.e) ? argv.e : argv.e ? [argv.e] : []) 38 | .forEach(function (env_var) { 39 | if(~env_var.indexOf('=')) { 40 | var split = env_var.split('='); 41 | args.env[split[0]] = split[1]; 42 | } 43 | }); 44 | 45 | // 46 | // Try to get tty size to give to child process 47 | // This allows shell to be resized to whatever is the current window size 48 | // 49 | var isatty = tty.isatty(1) && tty.isatty(2) 50 | , width = isatty 51 | ? process.stdout.getWindowSize 52 | ? process.stdout.getWindowSize(1)[0] 53 | : tty.getWindowSize 54 | ? tty.getWindowSize()[1] 55 | : null 56 | : null 57 | ; 58 | 59 | // 60 | // If we found something define it as MAXCOLS internal var 61 | // 62 | if(typeof width === 'number') { 63 | args.env.SPECIFY_MAXCOLS = width; 64 | } 65 | 66 | // 67 | // This is being executed from a runner 68 | // 69 | args.env.SPECIFY_FROM_RUNNER = true; 70 | 71 | // 72 | // Set a reporter 73 | // 74 | args.env.SPECIFY_REPORTER = argv.r 75 | || process.env.SPECIFY_REPORTER 76 | || 'default'; 77 | 78 | // 79 | // For spawn 80 | // 81 | args.stdio = ['pipe', 'pipe', 'pipe']; 82 | 83 | // 84 | // Recurse each file/subdirectory in our test folder 85 | // 86 | for(var dir in dirs) { 87 | // 88 | // Get that file/subdirectory path 89 | // 90 | var dir_path = path.resolve(base_path, dirs[dir]); 91 | // 92 | // If its indeed a subdirectory 93 | // 94 | if(fs.statSync(dir_path).isDirectory()) { 95 | // 96 | // Read the files in the subdirectory 97 | // 98 | files = fs.readdirSync(dir_path); 99 | // 100 | // For each file 101 | // 102 | for (var file in files) { 103 | // 104 | // Get the file path and run it if the extension is `js` or `node` 105 | // 106 | var file_path = path.resolve(dir_path, files[file]); 107 | if(/\.js$|\.node$/.test(file_path)) { 108 | // 109 | // Put the test in the stack yo. 110 | // 111 | stack.push(file_path); 112 | } 113 | } 114 | } 115 | } 116 | 117 | // 118 | // Let's start running our tests 119 | // 120 | runTests(stack, true); 121 | 122 | // 123 | // ### function runTests(tests, previousStatus) 124 | // #### @tests {Array} Tests the need some runnin 125 | // #### @previousStatus {Boolean} To set failure from a previous call 126 | // 127 | // Runs all the tests. 128 | // 129 | function runTests(tests, previousStatus) { 130 | // 131 | // Get the file name we want to run 132 | // 133 | var test_path = tests[0]; 134 | 135 | // 136 | // If we have a file name 137 | // 138 | if(test_path) { 139 | 140 | // 141 | // Start a child process to run that test 142 | // 143 | var test_stream = spawn('node', [test_path], args); 144 | 145 | // 146 | // Reset our stdio buffer 147 | // 148 | buffer = ''; 149 | 150 | // 151 | // Pipe the stdout from the child process to what the user can see 152 | // on his screen 153 | // 154 | test_stream.stdout.pipe(process.stdout); 155 | test_stream.stderr.pipe(process.stdout); 156 | 157 | // 158 | // Buffer the data so we can use it to make global counts across different 159 | // child processes 160 | // 161 | test_stream.stdout.on('data', function (chunk) { 162 | buffer += chunk; 163 | }); 164 | 165 | // 166 | // Calculate these totals for this child 167 | // 168 | test_stream.on('close', function (exit) { 169 | // 170 | // The exit code from the child not being `0` means error 171 | // 172 | if(exit !== 0) { 173 | success = false; 174 | } 175 | // 176 | // Do the calculations, print out the results, recurse to runTests 177 | // 178 | ppTest(tests, buffer); 179 | }); 180 | 181 | test_stream.on('error', function (err) { 182 | // 183 | // We failed 184 | // 185 | success = false; 186 | console.log(err); 187 | }); 188 | } 189 | 190 | // 191 | // Since we have run all the tests and have no filename left 192 | // its time to run the summary of the suite 193 | // 194 | else { 195 | // 196 | // If we have data to summarize 197 | // 198 | if (counts.ok > 1 && counts.total > 1) { 199 | try { 200 | // 201 | // Get out reporter 202 | // 203 | var summarize = require('../reporters/' + args.env.SPECIFY_REPORTER); 204 | summarize('totals'); 205 | summarize('summary', 206 | { ok: counts.ok 207 | , fail: counts.total-counts.ok 208 | , notrun: 0 209 | , thrown: 0 210 | , _nostdout: true 211 | }); 212 | } catch (exc) {} 213 | } 214 | // 215 | // Make sure it fails if at least one test failed 216 | // 217 | return process.exit(success ? 0 : 1); 218 | } 219 | } 220 | 221 | // 222 | // ### ppTest (tests) 223 | // #### @tests {Array} Tests to prepare 224 | // 225 | // Prepate tests for `exec` 226 | // 227 | function ppTest (tests, stdout) { 228 | // 229 | // Get the path from current tests 230 | // 231 | var file_path = tests.shift(); 232 | 233 | // 234 | // Update totals, if possible and provided by reporter 235 | // 236 | var lines = stdout.split('\n'); 237 | 238 | // 239 | // If we actually got some new line separated test results 240 | // 241 | if(lines.length>0) { 242 | // 243 | // Get the line before the last (last is simply a new line in specify) 244 | // 245 | var last_line = lines[lines.length-2]; 246 | 247 | // 248 | // If we have a count in our last line that was spit out by the test 249 | // we want to see if there's a `/` 250 | // 251 | // Like in: 252 | // /tests/shared/nano.js 253 | // 254 | // ✔ 2/2 shared_nano:test 255 | // ✔ 2/2 summary 256 | // 257 | // or 258 | // ✔ /tests/design/query.js .............................. 12/12 259 | // 260 | // we are interested in the \d+\/\d+ part of it, so lets break it there 261 | // 262 | if(!last_line) { 263 | console.log(stdout); 264 | } 265 | else if(~last_line.indexOf('/')) { 266 | // 267 | // Get the last line and try to understand if we can extract counts 268 | // like 1/5 269 | // 270 | var values = last_line.split('/'); 271 | 272 | if(values.length > 1) { 273 | // 274 | // Transform values to integer. the worst way possible 275 | // 276 | // > +9 277 | // 9 278 | // > +'9' 279 | // 9 280 | // > +'9n' 281 | // NaN 282 | // 283 | // We split by space to trim out potential non numbers 284 | // 285 | // For `worked` the thing we want in the last word (length-1) on the 286 | // part of the split in the position before last (values length-2) 287 | // 288 | var worked = values[values.length-2].split(' '); 289 | worked = +worked[worked.length-1]; 290 | 291 | // 292 | // For total this is the first word (0) in the last position of the 293 | // split (values length-1) 294 | // 295 | var total = +values[values.length-1].split(' ')[0]; 296 | 297 | // 298 | // If these things are not numbers 299 | // 300 | if(!isNaN(worked) && !isNaN(total)) { 301 | // 302 | // Update our counts using the result from child process 303 | // When we end we can summarize all tests 304 | // 305 | counts.ok += worked; 306 | counts.total += total; 307 | // 308 | // Next! 309 | // 310 | lines.pop(); 311 | } 312 | } 313 | } 314 | } 315 | 316 | // 317 | // To iterate is human, <> 318 | // 319 | runTests(tests); 320 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "specify", 3 | "description": "bite sized node.js testing", 4 | "author": "nuno job (http://nunojob.com/)", 5 | "contributors": [ 6 | "David Trejo (http://dtrejo.com)", 7 | "Jarrett Cruger (https://github.com/jcrugzz)", 8 | "Isaac Z. Schlueter (http://izs.me)", 9 | "Jon Buckley (http://jbuckley.ca)" 10 | ], 11 | "version": "1.3.1", 12 | "main": "./specify.js", 13 | "homepage": "https://github.com/dscape/specify", 14 | "repository": { 15 | "type": "git", 16 | "url": "http://github.com/dscape/specify.git" 17 | }, 18 | "bugs": "http://github.com/dscape/specify/issues", 19 | "keywords": [ 20 | "test", 21 | "assert", 22 | "should", 23 | "tdd" 24 | ], 25 | "engines": { 26 | "node": ">=0.8.0" 27 | }, 28 | "scripts": { 29 | "test": "./test.sh" 30 | }, 31 | "bin": { 32 | "specify": "./bin/specify" 33 | }, 34 | "dependencies": { 35 | "difflet": "0.2.x", 36 | "colors": "0.6.x", 37 | "cycle": "1.0.x", 38 | "optimist": "0.3.x", 39 | "esprima": "1.0.x" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /reporters/compact.js: -------------------------------------------------------------------------------- 1 | var colors = require('colors') 2 | , tty = require('tty') 3 | , difflet = require('difflet') 4 | , decycle = require('cycle').decycle 5 | ; 6 | 7 | var isatty = tty.isatty(1) && tty.isatty(2) 8 | , width = process.env.SPECIFY_MAXCOLS 9 | ? process.env.SPECIFY_MAXCOLS 10 | : isatty 11 | ? process.stdout.getWindowSize 12 | ? process.stdout.getWindowSize(1)[0] 13 | : tty.getWindowSize 14 | ? tty.getWindowSize()[1] 15 | : 70 16 | : 70 17 | , current_test = "" 18 | , current_errors = {} 19 | ; 20 | 21 | module.exports = function compact_reporter(name, report, errors) { 22 | // starting a new file 23 | if(typeof report === "undefined") { 24 | current_test = name; 25 | current_errors = {}; 26 | } else if (name === "summary") { // finished testing 27 | var failed = report.fail + report.notrun 28 | , left = failed === 0 ? ('✔'.green) : ('✗'.red) 29 | , right = report.ok + '/' + (report.ok+failed) 30 | ; 31 | left += " " + current_test; 32 | var dots = new Array( 33 | Math.max(1, width - left.length - right.length)).join("."); 34 | console.log("%s %s %s", left, dots, right); 35 | current_test = ""; 36 | current_errors = {}; 37 | } else { // errors 38 | 39 | } 40 | }; -------------------------------------------------------------------------------- /reporters/default.js: -------------------------------------------------------------------------------- 1 | var colors = require('colors') 2 | , difflet = require('difflet') 3 | , decycle = require('cycle').decycle 4 | ; 5 | 6 | module.exports = function default_reporter(name, report, errors) { 7 | if(typeof report === "undefined") { 8 | console.log(); 9 | console.log(" " + name); 10 | return console.log(); 11 | } 12 | errors = errors || []; 13 | var failed = report.fail + report.notrun; 14 | var symbol = failed === 0 ? ('✔'.green) : ('✗'.red); 15 | var right = report.ok + '/' + (report.ok+failed); 16 | process.stdout.write(symbol + ' '); 17 | process.stdout.write(right + ' '); 18 | var d = ''; 19 | if(report.duration) { 20 | d = 'took '.grey + (report.duration + 'ms').green; 21 | } 22 | console.log(name.cyan + " " + d); 23 | errors.forEach(function(err) { 24 | if(typeof err === "string") { 25 | console.log('└───── '.grey + (err || "Error")); 26 | } else { 27 | console.log('└───── '.grey + (err.msg || "Error")); 28 | process.stdout.write('❝ '.grey + err.assert + ' '); 29 | if(err.assert === "ok") { 30 | console.log(err.args[0]); 31 | } else { 32 | console.log(); 33 | if(err.args[0] && err.args[1] && typeof err.args[0]==="object" && 34 | typeof err.args[1]==="object") { 35 | console.log( 36 | difflet({ indent : 2, comment : true }) 37 | .compare(err.args[0], err.args[1])); 38 | } else { 39 | var indexzero = decycle(err.args[0] || "undefined") 40 | , indexone = decycle(err.args[1] || "undefined") 41 | ; 42 | try { 43 | process.stdout.write(JSON.stringify(indexzero, null, 1).magenta); 44 | console.log((" // " + 45 | JSON.stringify(indexone, null, 1)).cyan); 46 | } catch (ex) { 47 | console.log(" // { \"circular\": \"⥁\"}".cyan); 48 | } 49 | } 50 | } 51 | } 52 | }); 53 | }; -------------------------------------------------------------------------------- /reporters/json.js: -------------------------------------------------------------------------------- 1 | var decycle = require('cycle').decycle; 2 | 3 | module.exports = function json_reporter(name, report, errors) { 4 | var json_report = 5 | { name : name 6 | , report : report 7 | , errors : errors 8 | }; 9 | console.log(JSON.stringify(decycle(json_report))); 10 | }; -------------------------------------------------------------------------------- /specify.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | , domain = require('domain') 3 | , esprima = require('esprima') 4 | , path = require('path'), colors 5 | , reporters = {} 6 | , assertions = 7 | [ 'ok', 'equal', 'notEqual', 'deepEqual', 'notDeepEqual' 8 | , 'strictEqual', 'notStrictEqual', 'ifError'] 9 | , err_count = 0 10 | , MAX_ERRORS = process.env.SPECIFY_MAX_ERRORS || 1000 11 | , CHECK_GLOBALS = process.env.SPECIFY_CHECK_GLOBALS ? true : false 12 | , GLOBALS = Object.keys(global) 13 | , startTime 14 | ; 15 | 16 | // read available reporters 17 | require('fs').readdirSync(path.join(__dirname, 'reporters')) 18 | .forEach(function(reporter) { 19 | reporters[reporter]=require(path.join(__dirname, 'reporters', reporter)); 20 | }); 21 | 22 | module.exports = (function specify() { 23 | var cache = [] 24 | , counts = { _totals: {ok: 0, fail: 0, notrun: 0, thrown: 0} } 25 | , spec, summary, def_summary, timer, current_domain 26 | , default_reporter = process.env.SPECIFY_REPORTER 27 | ? Object.keys(reporters) 28 | .indexOf(process.env.SPECIFY_REPORTER + ".js") === -1 29 | ? 'default' 30 | : process.env.SPECIFY_REPORTER 31 | : 'default' 32 | ; 33 | 34 | def_summary = summary = reporters[default_reporter + '.js']; 35 | 36 | function ensure_for(test, expect, tests, done) { 37 | var ensure = {}, count = expect, errored = []; 38 | counts[test] = {ok: 0, fail: 0, notrun: 0, thrown: 0}; 39 | counts[test].meta = {name: test, expected: expect, remaining: tests}; 40 | counts[test].meta.errored = errored; 41 | counts[test].meta.remaining_assertions = expect; 42 | 43 | assertions.forEach(function(assertion) { 44 | ensure[assertion] = function () { 45 | if(counts[test].thrown > 0) { 46 | return; 47 | } 48 | try { 49 | assert[assertion].apply(this,arguments); 50 | counts._totals.ok++; 51 | counts[test].ok++; 52 | } 53 | catch (err) { 54 | errored.push({ msg: err.message 55 | , assert: assertion, args: [].slice.call(arguments,0)}); 56 | counts._totals.fail++; 57 | counts[test].fail++; 58 | } 59 | count--; 60 | counts[test].meta.remaining_assertions = count; 61 | if(count === 0) { 62 | done(errored); 63 | } 64 | }; 65 | }); 66 | 67 | ensure.expect = function (nr) { count = nr; }; 68 | return ensure; 69 | 70 | } 71 | 72 | function check_globals () { 73 | var end_globals = Object.keys(global) 74 | , leaks = end_globals.filter(function (eg) { 75 | return !~GLOBALS.indexOf(eg); 76 | }); 77 | return leaks; 78 | } 79 | 80 | // 81 | // lifted from esprima's `detectnestedternary` example 82 | // 83 | function traverse(object, visitor) { 84 | var key 85 | , child 86 | ; 87 | 88 | visitor.call(null, object); 89 | for (key in object) { 90 | if (object.hasOwnProperty(key)) { 91 | child = object[key]; 92 | if (typeof child === 'object' && child !== null) { 93 | traverse(child, visitor); 94 | } 95 | } 96 | } 97 | } 98 | 99 | function run_tests(tests) { 100 | if(timer) { 101 | clearTimeout(timer); 102 | timer = undefined; 103 | } 104 | if(tests.length === 0) { 105 | var leaks = check_globals(); 106 | leaks.forEach(function (leak) { 107 | console.log('leak detected: ' + leak); 108 | }); 109 | counts._totals.duration = Date.now() - startTime; 110 | summary('summary', counts._totals); 111 | process.exit(counts._totals.fail === 0 ? 0 : -1); 112 | } 113 | else { 114 | var test = tests.shift() 115 | , name = test[0] 116 | , timeout = test[1] 117 | , f = test[2] 118 | , expect 119 | ; 120 | if(typeof timeout === "function") { 121 | f = timeout; 122 | timeout = undefined; 123 | } 124 | // Need to add () so that it's a complete JS script 125 | var program = esprima.parse("(" + f.toString() + ")") 126 | , vari 127 | ; 128 | 129 | // Get argument identifiers from the function passed to specify 130 | if (program.body[0].expression.type === "FunctionExpression") { 131 | vari = program.body[0].expression.params.map(function(value) { 132 | return value.name; 133 | }); 134 | } 135 | 136 | if(Array.isArray(vari) && vari.length > 0) { 137 | // Traverse program looking for assert.* function calls 138 | var numberOfAsserts = 0 139 | , expected = false 140 | ; 141 | traverse(program, function(node) { 142 | if (node.type === 'MemberExpression' && 143 | node.object.name === vari[0] && 144 | assertions.indexOf(node.property.name) !== -1) { 145 | numberOfAsserts++; 146 | } 147 | else if (node.type === 'MemberExpression' && 148 | node.object.name === vari[0] && 149 | node.property.name === 'expect') { 150 | // will be specified later on but before nextTick 151 | expected=true; 152 | } 153 | }); 154 | 155 | if(numberOfAsserts || expected) { 156 | expect = numberOfAsserts; 157 | current_domain = domain.create(); 158 | current_domain.on('error', domainHandler(name)); 159 | return current_domain.run(function () { 160 | if(timeout) { 161 | timer = setTimeout(function (){ 162 | throw new Error("Timeout"); 163 | }, timeout); 164 | } 165 | process.nextTick(function (){ 166 | var start = Date.now(); 167 | f(ensure_for(name, expect, tests, function (errors) { 168 | counts[name].duration = Date.now() - start; 169 | summary(name, counts[name], errors); 170 | run_tests(tests); 171 | })); 172 | }); 173 | }); 174 | } else { 175 | summary(name, {ok: 0, fail: 1, notrun: 0, thrown: 0}, 176 | [' you need to add at least one `'+ vari[0] + '.*` call']); 177 | } 178 | } else { 179 | summary(name, {ok: 0, fail: 1, notrun: 0, thrown: 0}, 180 | [' `assert` must be the first argument of your callback']); 181 | } 182 | counts._totals.fail++; 183 | run_tests(tests); 184 | } 185 | } 186 | 187 | spec = function specify_test(name, f) { 188 | cache.push([].slice.call(arguments,0)); 189 | }; 190 | 191 | spec.reporter = function (f) { 192 | if (typeof f === 'function') { 193 | summary = f; 194 | return; 195 | } 196 | else if (typeof f === 'string') { 197 | var reporter = reporters[f + '.js']; 198 | if(typeof reporter === 'function') { 199 | summary = reporter; 200 | return; 201 | } 202 | } 203 | summary = def_summary; 204 | }; 205 | 206 | spec.run = function run_all_tests(filter) { 207 | if(typeof filter === "function") { 208 | cache = [["main", filter]]; 209 | filter = []; 210 | } 211 | summary(module.parent.filename.replace(process.cwd(), "")); 212 | startTime = Date.now(); 213 | filter = typeof filter === "string" ? [filter] : filter; 214 | if(filter && filter.length !== 0) { 215 | var filtered_cache = []; 216 | filter.forEach(function (e) { 217 | cache.forEach(function (c){ 218 | var name = c[0]; 219 | if(name===e) filtered_cache.push(c); 220 | }); 221 | }); 222 | run_tests(filtered_cache); 223 | } 224 | else { 225 | run_tests(cache); 226 | } 227 | }; 228 | 229 | function domainHandler(test_name) { 230 | return function (err) { 231 | var current_test = counts[test_name].meta; 232 | if(counts[test_name].thrown > 0) { 233 | // ignore, this test already thrown at least once 234 | // lets just wait for the socket to c 235 | return; 236 | } 237 | err_count++; 238 | if(MAX_ERRORS === err_count) { 239 | err.message = "You have reached " + MAX_ERRORS + 240 | " errors so we decided to abort your tests\noriginal: " + 241 | err.message; 242 | throw err; 243 | } 244 | err = typeof err === "string" ? new Error(err) : err; // idiotpatching 245 | err.stacktrace = err.stack.split("\n").splice(1) 246 | .map(function (l) { return l.replace(/^\s+/,""); }); 247 | counts[current_test.name].notrun += current_test.remaining_assertions; 248 | counts._totals.notrun += current_test.remaining_assertions; 249 | counts[current_test.name].thrown++; 250 | counts._totals.thrown++; 251 | current_test.errored.push( 252 | {msg: err.message || err, assert: "equal", args: ["domain", err]}); 253 | summary(current_test.name, counts[current_test.name] 254 | , current_test.errored); 255 | run_tests(current_test.remaining); 256 | }; 257 | } 258 | 259 | return spec; 260 | })(); 261 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | node test/specify.js \ 2 | | sed 's/.\[[0-9][0-9]m//g' \ 3 | | sed 's/"at.*\(.*\)//' \ 4 | | sed 's/took.*ms//' \ 5 | | sed 's/"duration":[0-9]*/"duration":0/' \ 6 | | sed 's/"_idleStart".*//' \ 7 | | sed '/^[ \t]*$/d' \ 8 | > test/all.log 9 | node test/specify.js specify#throws specify#cascading_sync \ 10 | | sed 's/.\[[0-9][0-9]m//g' \ 11 | | sed 's/"at.*\(.*\)//' \ 12 | | sed 's/took.*ms//' \ 13 | | sed 's/"duration":[0-9]*/"duration":0/' \ 14 | | sed 's/"_idleStart".*//' \ 15 | | sed '/^[ \t]*$/d' \ 16 | > test/filters.log 17 | node test/specify/singletest.js \ 18 | | sed 's/.\[[0-9][0-9]m//g' \ 19 | | sed 's/took.*ms//' \ 20 | | sed 's/"duration":[0-9]*/"duration":0/' \ 21 | | sed 's/"_idleStart".*//' \ 22 | | sed '/^[ \t]*$/d' \ 23 | > test/single.log 24 | node bin/specify -e SPECIFY_REPORTER=compact \ 25 | > test/reporter.log 26 | node bin/specify -r compact \ 27 | > test/reporter2.log 28 | node bin/specify -r default \ 29 | | sed 's/.\[[0-9][0-9]m//g' \ 30 | | sed 's/"at.*\(.*\)//' \ 31 | | sed 's/took.*ms//' \ 32 | | sed 's/"duration":[0-9]*/"duration":0/' \ 33 | | sed 's/"_idleStart".*//' \ 34 | | sed '/^[ \t]*$/d' \ 35 | > test/default_reporter.log 36 | echo "all#1" 37 | diff test/all.log test/fixtures/all.txt 38 | if [ $? -eq 0 ]; then 39 | echo "filters#2" 40 | diff test/filters.log test/fixtures/filters.txt 41 | if [ $? -eq 0 ]; then 42 | echo "single#3" 43 | diff test/single.log test/fixtures/single.txt 44 | if [ $? -eq 0 ]; then 45 | echo "reporter#4" 46 | diff test/reporter.log test/fixtures/reporter.txt 47 | if [ $? -eq 0 ]; then 48 | echo "reporter_alt#5" 49 | diff test/reporter.log test/reporter2.log 50 | if [ $? -eq 0 ]; then 51 | echo "default_reporter#6" 52 | diff test/default_reporter.log test/fixtures/default_reporter.txt 53 | if [ $? -eq 0 ]; then 54 | echo "ok"; 55 | else 56 | exit 1; 57 | fi 58 | else 59 | exit 1; 60 | fi 61 | else 62 | exit 1; 63 | fi 64 | else 65 | exit 1; 66 | fi 67 | else 68 | exit 1; 69 | fi 70 | else 71 | exit 1; 72 | fi -------------------------------------------------------------------------------- /test/fixtures/all.txt: -------------------------------------------------------------------------------- 1 | /test/specify.js 2 | ✗ 0/1 specify:no_arguments_in_cb 3 | └───── `assert` must be the first argument of your callback 4 | ✗ 0/1 specify#no_assertions 5 | └───── you need to add at least one `assert.*` call 6 | ✔ 1/1 specify#dinosaurs 7 | ✗ 0/1 specify#wrong_var 8 | └───── you need to add at least one `dino.*` call 9 | ✔ 1/1 specify#sync 10 | ✔ 7/7 specify#all_assertions 11 | ✗ 1/3 specify#assertion_with_optional_message 12 | └───── this is the error you are looking for 13 | ❝ ok false 14 | └───── this will 15 | ❝ ok false 16 | specify#custom_pretty_print :: [{"msg":"i see dead people","assert":"ok","args":[false,"i see dead people"]}] 17 | ✗ 0/1 specify#ask_for_a_specific_reporter 18 | └───── back to default 19 | ❝ ok false 20 | specify#custom_pretty_print_just_name 21 | ✔ 1/1 specify#async 22 | ✗ 1/2 specify#timeout 23 | └───── Timeout 24 | ❝ equal 25 | "domain" // { 26 | "domain_thrown": true, 27 | "domain": { 28 | "members": [], 29 | "_events": {} 30 | }, 31 | "stacktrace": [ 32 | ] 33 | } 34 | ✔ 2/2 specify#timeout_after 35 | ✔ 5/5 specify#more_assertions_than_asserts 36 | ✗ 0/1 specify#differences:ok 37 | └───── Should be true 38 | ❝ ok false 39 | ✗ 0/1 specify#differences:equal 40 | └───── One is love 41 | ❝ equal 42 | 13 // 1 43 | ✗ 0/1 specify#differences:equal_undefined 44 | └───── One is love 45 | ❝ equal 46 | "undefined" // 1 47 | ✗ 0/1 specify#differences:notequal 48 | └───── One two 49 | ❝ notEqual 50 | 2 // 2 51 | ✗ 0/1 specify#differences:deepequal 52 | └───── Blooper 53 | ❝ deepEqual 54 | { 55 | "a" : { 56 | "b" : 3 // != 1 57 | } 58 | } 59 | ✗ 0/1 specify#differences:notdeepequal 60 | └───── Not Deep 61 | ❝ notDeepEqual 62 | { 63 | "a" : 1 64 | } 65 | ✗ 0/1 specify#differences:strictequal 66 | └───── Dont be like that 67 | ❝ strictEqual 68 | 5 // 3 69 | ✗ 0/1 specify#differences:notstrictequal 70 | └───── 3 4 knock on the door 71 | ❝ notStrictEqual 72 | 4 // 4 73 | ✗ 1/5 specify#circular_reference 74 | └───── Error 75 | ❝ equal 76 | { 77 | "abc": "Hello", 78 | "go": { 79 | "$ref": "$" 80 | } 81 | } // "undefined" 82 | └───── Error 83 | ❝ equal 84 | "undefined" // { 85 | "abc": "Hello", 86 | "go": { 87 | "$ref": "$" 88 | } 89 | } 90 | └───── Error 91 | ❝ equal 92 | { 93 | "abc" : "Hello", // != undefined 94 | "go" : { 95 | } // != undefined 96 | } 97 | └───── Error 98 | ❝ equal 99 | { 100 | // "abc" : "Hello", 101 | // "go" : {"abc":"Hello","go":{}} 102 | } 103 | ✗ 0/2 specify#cascading_sync 104 | └───── No error 105 | ❝ ok false 106 | └───── Cannot read property 'name' of undefined 107 | ❝ equal 108 | "domain" // { 109 | "domain_thrown": true, 110 | "domain": { 111 | "members": [], 112 | "_events": {} 113 | }, 114 | "stacktrace": [ 115 | ] 116 | } 117 | ✗ 0/1 specify#throws 118 | └───── bla 119 | ❝ equal 120 | "domain" // { 121 | "stacktrace": [ 122 | ] 123 | } 124 | {"name":"specify#json_reporter","report":{"ok":1,"fail":0,"notrun":0,"thrown":0,"meta":{"name":"specify#json_reporter","expected":1,"remaining":[["specify#comments",null],["specify#comments_arent_detected",null]],"errored":[],"remaining_assertions":0},"duration":0},"errors":{"$ref":"$[\"report\"][\"meta\"][\"errored\"]"}} 125 | {"name":"specify#comments","report":{"ok":2,"fail":0,"notrun":0,"thrown":0,"meta":{"name":"specify#comments","expected":2,"remaining":[["specify#comments_arent_detected",null]],"errored":[],"remaining_assertions":0},"duration":0},"errors":{"$ref":"$[\"report\"][\"meta\"][\"errored\"]"}} 126 | {"name":"specify#comments_arent_detected","report":{"ok":1,"fail":0,"notrun":0,"thrown":0,"meta":{"name":"specify#comments_arent_detected","expected":1,"remaining":[],"errored":[],"remaining_assertions":0},"duration":0},"errors":{"$ref":"$[\"report\"][\"meta\"][\"errored\"]"}} 127 | {"name":"summary","report":{"ok":25,"fail":21,"notrun":3,"thrown":3,"duration":0}} 128 | -------------------------------------------------------------------------------- /test/fixtures/default_reporter.txt: -------------------------------------------------------------------------------- 1 | /test/specify/compact.js 2 | ✗ 1/5 specify#circular_reference 3 | └───── Error 4 | ❝ equal 5 | { 6 | "abc": "Hello", 7 | "go": { 8 | "$ref": "$" 9 | } 10 | } // "undefined" 11 | └───── Error 12 | ❝ equal 13 | "undefined" // { 14 | "abc": "Hello", 15 | "go": { 16 | "$ref": "$" 17 | } 18 | } 19 | └───── Error 20 | ❝ equal 21 | { 22 | "abc" : "Hello", // != undefined 23 | "go" : { 24 | } // != undefined 25 | } 26 | └───── Error 27 | ❝ equal 28 | { 29 | // "abc" : "Hello", 30 | // "go" : {"abc":"Hello","go":{}} 31 | } 32 | ✗ 0/2 specify#cascading_sync 33 | └───── No error 34 | ❝ ok false 35 | └───── Cannot read property 'name' of undefined 36 | ❝ equal 37 | "domain" // { 38 | "domain_thrown": true, 39 | "domain": { 40 | "members": [], 41 | "_events": {} 42 | }, 43 | "stacktrace": [ 44 | ] 45 | } 46 | ✗ 1/2 specify#cascading_sync_first_works 47 | └───── Cannot read property 'name' of undefined 48 | ❝ equal 49 | "domain" // { 50 | "domain_thrown": true, 51 | "domain": { 52 | "members": [], 53 | "_events": {} 54 | }, 55 | "stacktrace": [ 56 | ] 57 | } 58 | ✗ 0/2 specify#does_this_run 59 | └───── Cannot read property 'name' of undefined 60 | ❝ equal 61 | "domain" // { 62 | "domain_thrown": true, 63 | "domain": { 64 | "members": [], 65 | "_events": {} 66 | }, 67 | "stacktrace": [ 68 | ] 69 | } 70 | ✗ 0/3 specify#they_all_blow 71 | └───── No error 72 | ❝ ok false 73 | └───── Cannot read property 'foobar' of undefined 74 | ❝ equal 75 | "domain" // { 76 | "domain_thrown": true, 77 | "domain": { 78 | "members": [], 79 | "_events": {} 80 | }, 81 | "stacktrace": [ 82 | ] 83 | } 84 | ✗ 0/1 specify#throws 85 | └───── bla 86 | ❝ equal 87 | "domain" // { 88 | "stacktrace": [ 89 | ] 90 | } 91 | ✗ 2/15 summary 92 | /test/specify/parallel.js 93 | ✗ 0/6 specify#parallel_uncaught 94 | └───── Error 95 | ❝ ok false 96 | └───── Cannot read property 'dog' of undefined 97 | ❝ equal 98 | "domain" // { 99 | "domain_thrown": true, 100 | "domain": { 101 | "members": [], 102 | "_events": {} 103 | }, 104 | "stacktrace": [ 105 | ] 106 | } 107 | ✗ 0/2 specify#also_throws 108 | └───── Error 109 | ❝ ok false 110 | └───── Cannot read property 'mouse' of undefined 111 | ❝ equal 112 | "domain" // { 113 | "domain_thrown": true, 114 | "domain": { 115 | "members": [], 116 | "_events": {} 117 | }, 118 | "stacktrace": [ 119 | ] 120 | } 121 | ✔ 1/1 specify#ok 122 | ✗ 1/9 summary 123 | /test/specify/singletest.js 124 | ✔ 1/1 main 125 | leak detected: LEAKING 126 | ✔ 1/1 summary 127 | totals 128 | ✗ 4/25 summary 129 | -------------------------------------------------------------------------------- /test/fixtures/filters.txt: -------------------------------------------------------------------------------- 1 | /test/specify.js 2 | ✗ 0/1 specify#throws 3 | └───── bla 4 | ❝ equal 5 | "domain" // { 6 | "stacktrace": [ 7 | ] 8 | } 9 | ✗ 0/2 specify#cascading_sync 10 | └───── No error 11 | ❝ ok false 12 | └───── Cannot read property 'name' of undefined 13 | ❝ equal 14 | "domain" // { 15 | "domain_thrown": true, 16 | "domain": { 17 | "members": [], 18 | "_events": {} 19 | }, 20 | "stacktrace": [ 21 | ] 22 | } 23 | ✗ 0/3 summary 24 | -------------------------------------------------------------------------------- /test/fixtures/reporter.txt: -------------------------------------------------------------------------------- 1 | ✗ /test/specify/compact.js ............................. 2/15 2 | ✗ /test/specify/parallel.js ............................. 1/9 3 | leak detected: LEAKING 4 | ✔ /test/specify/singletest.js ........................... 1/1 5 | ✗ totals ............................................... 4/25 6 | -------------------------------------------------------------------------------- /test/fixtures/single.txt: -------------------------------------------------------------------------------- 1 | /test/specify/singletest.js 2 | ✔ 1/1 main 3 | leak detected: LEAKING 4 | ✔ 1/1 summary 5 | -------------------------------------------------------------------------------- /test/specify.js: -------------------------------------------------------------------------------- 1 | var specify = require('../specify') 2 | , filters = process.argv.slice(2) 3 | ; 4 | 5 | specify('specify:no_arguments_in_cb', function() {}); 6 | 7 | specify('specify#no_assertions', function (assert) { 8 | return; 9 | }); 10 | 11 | specify('specify#dinosaurs', function(dino) { 12 | dino.ok({trex:true}); 13 | }); 14 | 15 | specify('specify#wrong_var', function(dino) { 16 | assert.ok({trex:"sad"}); 17 | }); 18 | 19 | specify('specify#sync', function(assert) { 20 | assert.ok(true); 21 | }); 22 | 23 | specify('specify#all_assertions', function(assert) { 24 | assert.ok(true); 25 | assert.equal(1,1); 26 | assert.notEqual(1,2); 27 | assert.deepEqual({a:1}, {a:1}); 28 | assert.notDeepEqual({a:1}, {a:2}); 29 | assert.strictEqual(3, 3); 30 | assert.notStrictEqual(3, 4); 31 | }); 32 | 33 | specify('specify#assertion_with_optional_message', function(assert) { 34 | assert.ok(false, "this is the error you are looking for"); 35 | assert.ok(true, "this won't appear"); 36 | assert.ok(false, "this will"); 37 | }); 38 | 39 | specify('specify#custom_pretty_print', function(assert) { 40 | // set a custom reporter pretty print function 41 | specify.reporter(function (name, report, errors) { 42 | console.log(name + ' :: ' + JSON.stringify(errors)); 43 | }); 44 | assert.ok(false, 'i see dead people'); 45 | }); 46 | 47 | specify('specify#ask_for_a_specific_reporter', function(assert) { 48 | // reset reporter function 49 | specify.reporter('default'); 50 | assert.ok(false, 'back to default'); 51 | }); 52 | 53 | specify('specify#custom_pretty_print_just_name', function(assert) { 54 | // set a custom repoter pretty print function 55 | specify.reporter(function (name, report, errors) { 56 | console.log(name); 57 | }); 58 | assert.ok(false, 'i see dead people'); 59 | assert.ok(true); 60 | }); 61 | 62 | specify('specify#async', function(assert) { 63 | // reset reporter function 64 | specify.reporter(); 65 | setTimeout(function () { 66 | assert.ok(true, "was true"); 67 | }, 1); 68 | }); 69 | 70 | specify('specify#timeout', 50, function(assert) { 71 | assert.ok(true); 72 | setTimeout(function () { 73 | assert.ok(true, "was true"); 74 | }, 100); 75 | }); 76 | 77 | specify('specify#timeout_after', 100, function(assert) { 78 | assert.ok(true); 79 | setTimeout(function () { 80 | assert.ok(true, "was true"); 81 | }, 10); 82 | }); 83 | 84 | specify('specify#more_assertions_than_asserts', function(assert) { 85 | assert.expect(5); 86 | for(var i in [1,2,3,4,5]) { 87 | assert.equal(i,i); 88 | } 89 | }); 90 | 91 | specify('specify#differences:ok', function(assert) { 92 | assert.ok(false, "Should be true"); 93 | }); 94 | 95 | specify('specify#differences:equal', function(assert) { 96 | assert.equal(13,1, "One is love"); 97 | }); 98 | 99 | specify('specify#differences:equal_undefined', function(assert) { 100 | assert.equal(undefined,1, "One is love"); 101 | }); 102 | 103 | specify('specify#differences:notequal', function(assert) { 104 | assert.notEqual(2,2, "One two"); 105 | }); 106 | 107 | specify('specify#differences:deepequal', function(assert) { 108 | assert.deepEqual({a: {b: 1}}, {a: {b: 3}}, "Blooper"); 109 | }); 110 | 111 | specify('specify#differences:notdeepequal', function(assert) { 112 | assert.notDeepEqual({a:1}, {a:1}, "Not Deep"); 113 | }); 114 | 115 | specify('specify#differences:strictequal', function(assert) { 116 | assert.strictEqual(5, 3, "Dont be like that"); 117 | }); 118 | 119 | specify('specify#differences:notstrictequal', function(assert) { 120 | assert.notStrictEqual(4, 4, "3 4 knock on the door"); 121 | }); 122 | 123 | specify('specify#circular_reference', function(assert) { 124 | function foo() { 125 | this.abc = "Hello"; this.go = this; 126 | return this; 127 | } 128 | var c = new foo(); 129 | assert.equal(c,c); 130 | assert.equal(c,undefined); 131 | assert.equal(undefined,c); 132 | assert.equal({},c); 133 | assert.equal(c,{}); 134 | }); 135 | 136 | specify('specify#cascading_sync', function(assert) { 137 | var err = new Error("Testing") 138 | , body 139 | ; 140 | assert.ok(!err, "No error"); 141 | assert.equal(body.name, "Body has name"); 142 | }); 143 | 144 | specify('specify#throws', function(assert) { 145 | throw "bla"; 146 | assert.ok(true); 147 | }); 148 | 149 | specify('specify#json_reporter', function (assert) { 150 | specify.reporter('json'); 151 | assert.ok(true); 152 | }); 153 | 154 | specify('specify#comments', function (assert) { 155 | assert.expect(2); 156 | assert.ok(true); 157 | //assert.ok(false); 158 | assert.ok(true); 159 | }); 160 | 161 | specify('specify#comments_arent_detected', function (assert) { 162 | assert.ok(true); 163 | //assert.ok(true); 164 | /*assert.ok(true);*/ 165 | }); 166 | 167 | specify.run(filters); 168 | -------------------------------------------------------------------------------- /test/specify/compact.js: -------------------------------------------------------------------------------- 1 | var specify = require('../../specify'); 2 | 3 | specify('specify#circular_reference', function(assert) { 4 | function foo() { 5 | this.abc = "Hello"; this.go = this; 6 | return this; 7 | } 8 | var c = new foo(); 9 | assert.equal(c,c); 10 | assert.equal(c,undefined); 11 | assert.equal(undefined,c); 12 | assert.equal({},c); 13 | assert.equal(c,{}); 14 | }); 15 | 16 | specify('specify#cascading_sync', function(assert) { 17 | var err = new Error("Testing") 18 | , body 19 | ; 20 | assert.ok(!err, "No error"); 21 | assert.equal(body.name, "Body has name"); 22 | }); 23 | 24 | specify('specify#cascading_sync_first_works', function(assert) { 25 | var err 26 | , body 27 | ; 28 | assert.ok(!err, "No error"); 29 | assert.equal(body.name, "Body has name"); 30 | }); 31 | 32 | 33 | specify('specify#does_this_run', function(assert) { 34 | var err = new Error("Testing") 35 | , body 36 | ; 37 | assert.equal(body.name, "Body has name"); 38 | setTimeout(function() { assert.ok(!err, "No error"); }, 10); 39 | }); 40 | 41 | specify('specify#they_all_blow', function(assert) { 42 | var err = new Error("Testing") 43 | , body 44 | ; 45 | assert.ok(!err, "No error"); 46 | assert.equal(body.foobar, "Body has foobar"); 47 | assert.equal(body.name, "Body has name"); 48 | }); 49 | 50 | specify('specify#throws', function(assert) { 51 | throw "bla"; 52 | assert.ok(true); 53 | }); 54 | 55 | specify.run(); -------------------------------------------------------------------------------- /test/specify/parallel.js: -------------------------------------------------------------------------------- 1 | var specify = require('../../specify') 2 | , error = new Error("Sockets. Not really") 3 | ; 4 | 5 | specify('specify#parallel_uncaught', 50, function(assert) { 6 | // both throw, in different times, same test 7 | setTimeout(function (restaurant) { 8 | assert.ok(!error); 9 | assert.ok(restaurant.dog); 10 | assert.ok(false); 11 | }, 10); 12 | setTimeout(function (bar) { 13 | assert.ok(!error); 14 | assert.ok(bar.cat); 15 | assert.ok(true); 16 | }, 20); 17 | }); 18 | 19 | specify('specify#also_throws', 50, function(assert) { 20 | setTimeout(function (coffeeplace) { 21 | assert.ok(!error); 22 | assert.ok(coffeeplace.mouse); 23 | }, 10); 24 | }); 25 | 26 | specify('specify#ok', 50, function(assert) { 27 | assert.ok(true); 28 | }); 29 | 30 | specify.run(); -------------------------------------------------------------------------------- /test/specify/singletest.js: -------------------------------------------------------------------------------- 1 | var specify = require('../../specify'); 2 | 3 | specify.run( 4 | function(assert) { 5 | LEAKING = true; 6 | setTimeout(function () { assert.ok(true, "was true"); }, 1); 7 | // assert.ok(true, 'this is a comment') 8 | /* 9 | assert.ok('multiline too'); 10 | */ 11 | } 12 | ); --------------------------------------------------------------------------------