├── .gitignore ├── .jshintignore ├── .jshintrc ├── .travis.yml ├── LICENSE ├── README.md ├── context ├── _instrument-tape-and-run.js ├── get-tap-folder.js └── index.js ├── index.js ├── man └── itape.1 ├── modes ├── debug.js ├── fail.js └── trace.js ├── package.json ├── scripts └── create-man.sh ├── test └── index.js └── usage.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.a 8 | *.o 9 | *.so 10 | *.node 11 | 12 | # Node Waf Byproducts # 13 | ####################### 14 | .lock-wscript 15 | build/ 16 | autom4te.cache/ 17 | 18 | # Node Modules # 19 | ################ 20 | # Better to let npm install these from the package.json defintion 21 | # rather than maintain this manually 22 | node_modules/ 23 | 24 | # Packages # 25 | ############ 26 | # it's better to unpack these files and commit the raw source 27 | # git has its own built in compression methods 28 | *.7z 29 | *.dmg 30 | *.gz 31 | *.iso 32 | *.jar 33 | *.rar 34 | *.tar 35 | *.zip 36 | 37 | # Logs and databases # 38 | ###################### 39 | *.log 40 | dump.rdb 41 | *.tap 42 | *.xml 43 | 44 | # OS generated files # 45 | ###################### 46 | .DS_Store? 47 | .DS_Store 48 | ehthumbs.db 49 | Icon? 50 | Thumbs.db 51 | coverage 52 | 53 | # Text Editor Byproducts # 54 | ########################## 55 | *.sw? 56 | .idea/ 57 | 58 | # Python object code 59 | ########################## 60 | *.py[oc] 61 | 62 | # All translation files # 63 | ######################### 64 | static/translations-s3/ 65 | package-lock.json 66 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | .gitignore -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": false, 3 | "camelcase": true, 4 | "curly": false, 5 | "eqeqeq": true, 6 | "forin": true, 7 | "immed": true, 8 | "indent": 4, 9 | "latedef": "nofunc", 10 | "newcap": true, 11 | "noarg": true, 12 | "nonew": true, 13 | "plusplus": false, 14 | "quotmark": false, 15 | "regexp": false, 16 | "undef": true, 17 | "unused": true, 18 | "strict": false, 19 | "trailing": true, 20 | "node": true, 21 | "noempty": true, 22 | "maxdepth": 4, 23 | "maxparams": 4, 24 | "newcap": false, 25 | "globalstrict": true, 26 | "globals": { 27 | "console": true, 28 | "Buffer": true, 29 | "setTimeout": true, 30 | "clearTimeout": true, 31 | "setInterval": true, 32 | "clearInterval": true 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.8" 4 | - "0.10" 5 | - "0.11" 6 | before_install: npm i npm@latest -g 7 | script: npm run travis 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 itape. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # itape 2 | 3 | An interactive tape runner. 4 | 5 | ## Concept 6 | 7 | Interactive tape (`itape` for short) is a modal test runner. 8 | 9 | It behaves exactly like node except it has a few more features. 10 | The main trick it uses is to remember your last test run. 11 | 12 | Because it knows your test history it can easily re-run 13 | your tests and print just the failures or jump straight into 14 | the debugger. 15 | 16 | ## Usage 17 | 18 | - `itape test/index.js` Use `itape` just like `node`. 19 | - `itape --fail test/index.js` Print only failures 20 | - `itape --trace test/index.js` Print only failures and trace! 21 | - `itape --debug test/index.js` Print only failures and debug! 22 | 23 | ## Trace mode 24 | 25 | The trace mode uses the `itape` key in your package.json. 26 | You should configure it like: 27 | 28 | ``` 29 | "itape": { 30 | "trace": { 31 | "debuglog": [ 32 | "typedrequesthandler" 33 | ], 34 | "leakedHandles": true, 35 | "formatStack": true 36 | } 37 | } 38 | ``` 39 | 40 | Here we enable `debuglog` and `leaked-handles` only when 41 | trace is on. 42 | 43 | The `--trace` utility means you do not have to remember which 44 | debuglog modules to enable. 45 | 46 | ## Debug mode 47 | 48 | The debug mode uses will re-run your tests with the debugger on. 49 | It will place a breakpoint at EVERY failed assertion. 50 | 51 | This means you can just hit `repl` and inspect the state around 52 | your failed assertion without changing the code of your tests! 53 | -------------------------------------------------------------------------------- /context/_instrument-tape-and-run.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*global process*/ 4 | var $process = process; 5 | var resolve = require('resolve'); 6 | var path = require('path'); 7 | 8 | var env = $process.env; 9 | var testProgram = env.ITAPE_NPM_TEST_PROGRAM; 10 | var baseDir = path.dirname(testProgram); 11 | 12 | var leakedHandles = env.ITAPE_NPM_LEAKED_HANDLES; 13 | if (leakedHandles) { 14 | leakedHandles = JSON.parse(leakedHandles); 15 | 16 | var leakedHandlesFile = resolve.sync('leaked-handles', { 17 | basedir: baseDir 18 | }); 19 | var leakedHandlesModule = require(leakedHandlesFile); 20 | if (typeof leakedHandles === 'object') { 21 | leakedHandlesModule.set(leakedHandles); 22 | } 23 | } 24 | 25 | var formatStack = env.ITAPE_NPM_FORMAT_STACK; 26 | if (formatStack) { 27 | formatStack = JSON.parse(formatStack); 28 | 29 | var formatStackFile = resolve.sync('format-stack', { 30 | basedir: baseDir 31 | }); 32 | var formatStackModule = require(formatStackFile); 33 | if (typeof formatStack === 'object') { 34 | formatStackModule.set(formatStack); 35 | } else { 36 | formatStackModule.set({ 37 | traces: 'short' 38 | }); 39 | } 40 | } 41 | 42 | var whiteList = env.ITAPE_NPM_TAPE_WHITELIST; 43 | if (whiteList) { 44 | whiteList = JSON.parse(whiteList); 45 | instrumentTape(whiteList); 46 | } 47 | 48 | require(testProgram); 49 | 50 | function instrumentTape(whiteList) { 51 | var tapeTestFile 52 | try { 53 | tapeTestFile = resolve.sync('tape/lib/test', { 54 | basedir: baseDir 55 | }); 56 | } catch (origErr) { 57 | try { 58 | tapeTestFile = resolve.sync('@pre-bundled/tape/lib/test', { 59 | basedir: baseDir 60 | }); 61 | } catch (_) { 62 | throw origErr 63 | } 64 | } 65 | var tapeTest = require(tapeTestFile); 66 | 67 | var $run = tapeTest.prototype.run; 68 | tapeTest.prototype.run = function fakeRun() { 69 | if (whiteList.indexOf(this.name) === -1) { 70 | this._cb = null; 71 | } 72 | $run.apply(this, arguments); 73 | }; 74 | } 75 | -------------------------------------------------------------------------------- /context/get-tap-folder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var process = require('process'); 4 | var parents = require('parents'); 5 | var fs = require('fs'); 6 | var path = require('path'); 7 | var getHomePath = require('home-path'); 8 | var mkdirp = require('mkdirp'); 9 | 10 | var _cache; 11 | 12 | module.exports = getTapFolderInfo; 13 | 14 | function getTapFolderInfo() { 15 | if (_cache) { 16 | return _cache; 17 | } 18 | 19 | var regex; 20 | if (path.sep === "\\") { 21 | regex = new RegExp("\\\\", 'g'); 22 | } else { 23 | regex = new RegExp(path.sep, 'g'); 24 | } 25 | 26 | var packageFolder = findPackage(); 27 | var cacheFolder = path.join( 28 | getHomePath(), 29 | '.config', 30 | 'itape', 31 | packageFolder.replace(regex, '%') 32 | .replace(new RegExp(":", 'g'), '%') 33 | ); 34 | mkdirp.sync(cacheFolder); 35 | 36 | var result = { 37 | cacheFolder: cacheFolder, 38 | packageFolder: packageFolder 39 | }; 40 | 41 | _cache = result; 42 | 43 | return result; 44 | } 45 | 46 | function findPackage() { 47 | var folders = parents(process.cwd()); 48 | var packages = folders.map(function toFile(folder) { 49 | return path.join(folder, 'package.json'); 50 | }); 51 | 52 | var packageFolder = null; 53 | packages.some(function findpackage(packageFile) { 54 | var exists = fs.existsSync(packageFile); 55 | if (exists) { 56 | packageFolder = path.dirname(packageFile); 57 | } 58 | return exists; 59 | }); 60 | 61 | if (!packageFolder) { 62 | throw new Error('could not find package.json'); 63 | } 64 | return packageFolder; 65 | } 66 | -------------------------------------------------------------------------------- /context/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var spawn = require('child_process').spawn; 6 | var process = require('process'); 7 | var TapParser = require('tap-out').Parser; 8 | var Transform = require('readable-stream').Transform; 9 | var extend = require('xtend'); 10 | var mutableExtend = require('xtend/mutable'); 11 | 12 | var getTapFolderInfo = require('./get-tap-folder.js'); 13 | 14 | var testRunner = path.join(__dirname, 15 | '_instrument-tape-and-run.js'); 16 | 17 | module.exports = Context; 18 | 19 | function Context(argv) { 20 | if (!(this instanceof Context)) { 21 | return new Context(argv); 22 | } 23 | 24 | var tapFolder = getTapFolderInfo(); 25 | var cacheFolder = tapFolder.cacheFolder; 26 | var packageFolder = tapFolder.packageFolder; 27 | 28 | this._extraEnv = {}; 29 | this._stdoutFilter = null; 30 | this._debugMode = false; 31 | this._breakpoints = null; 32 | this._argv = []; 33 | 34 | this._lastFilePathFile = path.join(cacheFolder, 35 | 'last-run-file.log'); 36 | this._lastRunTapFile = path.join(cacheFolder, 'last-run.tap'); 37 | 38 | this.options = { 39 | trace: argv.trace, 40 | fail: argv.fail, 41 | debug: argv.debug, 42 | shortHelp: argv.h, 43 | help: argv.help, 44 | leakedHandles: argv['leaked-handles'], 45 | formatStack: argv['format-stack'], 46 | color: argv.color === undefined ? true : argv.color 47 | }; 48 | this.testProgram = argv._[0]; 49 | this.lastFilePath = safeReadFile(this._lastFilePathFile); 50 | this.lastRunTap = safeReadFile(this._lastRunTapFile); 51 | this.packageFile = path.join(packageFolder, 'package.json'); 52 | 53 | var parser = TapParser(); 54 | this.lastRunTap.split('\n') 55 | .forEach(parser.handleLine.bind(parser)); 56 | 57 | this.tapOutput = parser.results; 58 | } 59 | 60 | var proto = Context.prototype; 61 | 62 | proto.setCLIArg = function setCLIArg(key, value) { 63 | this._argv = this._argv.concat(['--' + key, value]); 64 | }; 65 | 66 | proto.setTestEnvironment = function setTestEnvironment(obj) { 67 | mutableExtend(this._extraEnv, obj); 68 | }; 69 | 70 | proto.setStdoutFilter = function setStdoutFilter(filter) { 71 | this._stdoutFilter = filter; 72 | }; 73 | 74 | proto.setDebugMode = function setDebugMode(bool) { 75 | this._debugMode = bool; 76 | }; 77 | 78 | proto.setBreakPoints = function setBreakPoints(points) { 79 | this._breakpoints = points; 80 | }; 81 | 82 | proto.spawnTest = function spawnTest() { 83 | fs.writeFileSync(this._lastFilePathFile, this.testProgram); 84 | 85 | var child = startChildTest(this); 86 | reportChildTest(this, child); 87 | setBreakPoints(this, child); 88 | 89 | return child; 90 | }; 91 | 92 | function startChildTest(ctx) { 93 | // trick eslint. 94 | var $process = process; 95 | 96 | var args = [testRunner].concat(ctx._argv); 97 | if (ctx._debugMode) { 98 | args.unshift('debug'); 99 | } 100 | 101 | args.push('--color=' + ctx.options.color); 102 | 103 | var child = spawn('node', args, { 104 | cwd: $process.cwd(), 105 | env: extend($process.env, { 106 | ITAPE_NPM_TEST_PROGRAM: 107 | path.resolve(ctx.testProgram) 108 | }, ctx._extraEnv) 109 | }); 110 | 111 | return child; 112 | } 113 | 114 | function reportChildTest(ctx, child) { 115 | var stdout = child.stdout; 116 | 117 | if (ctx._stdoutFilter) { 118 | stdout = filterStream(stdout, ctx._stdoutFilter); 119 | } 120 | 121 | if (ctx._debugMode) { 122 | process.stdin.pipe(child.stdin); 123 | } 124 | 125 | stdout.pipe(process.stdout); 126 | child.stderr.pipe(process.stderr); 127 | 128 | if (!ctx._debugMode) { 129 | // TODO. do not write to TAP file for invalid TAP. 130 | // i.e. a crashed uncaught exception child process. 131 | var writeStream = fs.createWriteStream(ctx._lastRunTapFile); 132 | child.stdout.pipe(writeStream, { 133 | end: false 134 | }); 135 | 136 | child.on('exit', function onErr(code) { 137 | if (code === 8) { 138 | writeStream.write('not ok 999 process crashed\n'); 139 | } 140 | 141 | writeStream.end(); 142 | }); 143 | } 144 | } 145 | 146 | function waitUntil(stream, phrase, listener) { 147 | var lines = ''; 148 | stream.on('data', function checkLine(buf) { 149 | lines += String(buf); 150 | 151 | if (lines.indexOf(phrase) >= 0) { 152 | listener(); 153 | stream.removeListener('data', checkLine); 154 | } 155 | }); 156 | } 157 | 158 | function setBreakPoints(ctx, child) { 159 | if (!ctx._breakpoints) { 160 | return; 161 | } 162 | 163 | waitUntil(child.stdout, 'break in', flushBreakpoints); 164 | 165 | function flushBreakpoints() { 166 | ctx._breakpoints.forEach(function set(bp) { 167 | var cmd = 'sb(' + 168 | JSON.stringify(bp.file) + ',' + 169 | JSON.stringify(bp.line) + ');\n'; 170 | 171 | child.stdin.write(cmd); 172 | }); 173 | 174 | child.stdin.write('cont\n'); 175 | } 176 | } 177 | 178 | function filterStream(stream, lambda) { 179 | var filter = new Transform(); 180 | filter._transform = function filter(chunk, _, cb) { 181 | if (lambda(chunk)) { 182 | this.push(chunk); 183 | } 184 | cb(); 185 | }; 186 | 187 | return stream.pipe(filter); 188 | } 189 | 190 | function safeReadFile(fileName) { 191 | if (fs.existsSync(fileName)) { 192 | return fs.readFileSync(fileName, 'utf8'); 193 | } else { 194 | return ''; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | var console = require('console'); 6 | var path = require('path'); 7 | var process = require('process'); 8 | var parseArgs = require('minimist'); 9 | var spawn = require('child_process').spawn; 10 | 11 | var Context = require('./context/'); 12 | var failMode = require('./modes/fail.js'); 13 | var traceMode = require('./modes/trace.js'); 14 | var debugMode = require('./modes/debug.js'); 15 | 16 | module.exports = main; 17 | 18 | if (require.main === module) { 19 | main(parseArgs(process.argv.slice(2), { 20 | boolean: [ 21 | 'fail', 'trace', 'debug', 'help', 'h', 22 | 'leaked-handles', 'format-stack', 'color' 23 | ] 24 | })); 25 | } 26 | 27 | 28 | /*Usage 29 | 30 | `itape test/index.js` to capture TAP 31 | `itape --fail test/index.js` to run in failure mode. 32 | fix tests 33 | `itape --fail test/index.js` to run in failure mode again. 34 | 35 | We write the TAP output of every run to the correct files. 36 | 37 | We only write TAP output when we are done. 38 | 39 | */ 40 | 41 | function main(argv) { 42 | /*eslint max-complexity: [2, 15]*/ 43 | /*eslint max-statements: [2, 25]*/ 44 | var context = Context(argv); 45 | 46 | if (context.options.shortHelp) { 47 | return printShortHelp(); 48 | } 49 | if (context.options.help) { 50 | return printHelp(); 51 | } 52 | 53 | // - if no filename, just bail. 54 | if (!context.testProgram) { 55 | return printShortHelp(); 56 | } 57 | 58 | // - if no last-run-file, run normal 59 | // - if last-run-file different run normal 60 | // - if no tap-output run normal 61 | if ( 62 | !context.lastFilePath || 63 | context.lastFilePath !== context.testProgram || 64 | !context.lastRunTap 65 | ) { 66 | printMode('normal', 'no previous run'); 67 | return context.spawnTest(); 68 | } 69 | 70 | // - if `--trace` then turn on tracing 71 | if (context.options.trace || 72 | context.options.leakedHandles || 73 | context.options.formatStack 74 | ) { 75 | printMode('trace', 'trace flag'); 76 | traceMode(context); 77 | } 78 | 79 | // - if tap-output contains no fails, run normal 80 | if (context.tapOutput.fail.length === 0) { 81 | printMode('normal', 'no failing tests'); 82 | return context.spawnTest(); 83 | } 84 | 85 | // - if `--debug` then turn on debug 86 | if (context.options.debug) { 87 | printMode('debug', 'debug flag'); 88 | debugMode(context); 89 | } 90 | 91 | // - if `--fail` or `--trace` or `--debug` 92 | if (context.options.fail || 93 | context.options.trace || 94 | context.options.debug 95 | ) { 96 | // - else run in fail mode 97 | printMode('fail', 'failure mode'); 98 | failMode(context); 99 | } else { 100 | printMode('normal', 'default mode'); 101 | } 102 | 103 | context.spawnTest(); 104 | } 105 | 106 | function printMode(mode, reason) { 107 | /*eslint no-console: 0*/ 108 | console.log('[itape]: ' + mode + ' mode (' + reason + ')'); 109 | } 110 | 111 | function printShortHelp() { 112 | console.log('usage: itape [--help] [--fail] [--trace] [--debug]'); 113 | console.log(' [--leaked-handles[=] [--format-stack[=]]'); 114 | console.log(' [-h] [--color] '); 115 | } 116 | 117 | function printHelp() { 118 | var options = { 119 | cwd: process.cwd(), 120 | env: process.env, 121 | setsid: false, 122 | customFds: [0, 1, 2] 123 | }; 124 | 125 | spawn('man', [path.join(__dirname, 'man', 'itape.1')], options); 126 | } 127 | -------------------------------------------------------------------------------- /man/itape.1: -------------------------------------------------------------------------------- 1 | .TH "ITAPE" "1" "April 2015" "v1.7.1" "itape" 2 | .SH "NAME" 3 | \fBitape\fR \- modal test runner 4 | .SH SYNOPSIS 5 | .P 6 | \fBitape\fR [\-\-help] [\-\-fail] [\-\-trace] [\-\-debug] 7 | [\-\-leaked\-handles[=] [\-\-format\-stack[=]] 8 | [\-h] 9 | .SH DESCRIPTION 10 | .P 11 | \fBitape\fR is a modal test runner\. It works exactly like \fBnode\fR and 12 | is instrumented to support the \fBtape\fR testing framework\. 13 | .P 14 | It supports multiple modes; a failure mode; a debugging mode 15 | and a tracing mode\. 16 | .SH EXAMPLES 17 | .RS 0 18 | .IP \(bu 2 19 | \fBitape test/index\.js\fR 20 | .IP \(bu 2 21 | \fBitape \-\-fail test/index\.js\fR 22 | .IP \(bu 2 23 | \fBitape \-\-trace test/index\.js\fR 24 | 25 | .RE 26 | .SH OPTIONS 27 | .P 28 | \fB\-\-fail\fR 29 | Runs your tests in failure mode\. \fBitape\fR will only run test 30 | blocks that failed last time\. It also filters out all TAP 31 | noise and only shows \fBnot ok\fR\|\. 32 | .P 33 | \fB\-\-trace\fR 34 | Runs your test in trace mode\. Trace mode implies failure mode\. 35 | Trace mode is defined by your package\.json \fB"itape"\fR 36 | configuration\. 37 | .P 38 | .RS 2 39 | .nf 40 | For example: 41 | 42 | ``` 43 | "itape": { 44 | "trace": { 45 | "debuglog": [ 46 | "typedrequesthandler" 47 | ], 48 | "leakedHandles": true, 49 | "formatStack": true 50 | } 51 | } 52 | ``` 53 | .fi 54 | .RE 55 | .P 56 | \fB\-\-debug\fR 57 | Runs your tests in debug mode\. Debug mode implies failure mode\. 58 | When your program is running in debug mode we run your process 59 | with the node CLI debugger and set a break point on every 60 | failed assertion 61 | .P 62 | \fB\-\-leaked\-handles[=]\fR 63 | Runs your tests with the \fBleaked\-handles\fR module included\. 64 | .P 65 | .RS 2 66 | .nf 67 | You can pass a value to configure `leaked\-handles` itself\. 68 | For example: 69 | 70 | itape \-\-leaked\-handles='{"fullStack": true, "timeout": 1000, "debugSockets": true}' test/index\.js 71 | .fi 72 | .RE 73 | .P 74 | \fB\-\-format\-stack[=]\fR 75 | Runs your tests with the \fBformat\-stack\fR module included\. 76 | .P 77 | .RS 2 78 | .nf 79 | You can pass a value to configure `format\-stack` itself\. 80 | For example: 81 | 82 | itape \-\-format\-stack='{"traces":"long"}' test/index\.js 83 | .fi 84 | .RE 85 | .P 86 | \fB\-\-help\fR 87 | Displays help information 88 | .P 89 | \fB\-h\fR 90 | Displays short help 91 | .SH BUGS 92 | .P 93 | Please report any bugs to http://github\.com/Raynos/itape 94 | .SH LICENCE 95 | .P 96 | MIT Licenced 97 | .SH SEE ALSO 98 | .P 99 | \fBtape(1), node\.js(1)\fR 100 | 101 | -------------------------------------------------------------------------------- /modes/debug.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = debugMode; 4 | 5 | function debugMode(ctx) { 6 | ctx.setDebugMode(true); 7 | 8 | var tapOutput = ctx.tapOutput; 9 | var fail = tapOutput.fail; 10 | 11 | var errors = fail.map(function getError(x) { 12 | return x.error.at; 13 | }); 14 | 15 | var locations = errors.map(function parse(x) { 16 | var line = x.line; 17 | var parts = x.file.split(' '); 18 | 19 | var fileName = null; 20 | if (parts[1]) { 21 | fileName = parts[1].substr(1); 22 | } else { 23 | fileName = x.file; 24 | } 25 | 26 | return { 27 | file: fileName, line: line 28 | }; 29 | }); 30 | 31 | ctx.setBreakPoints(locations); 32 | } 33 | -------------------------------------------------------------------------------- /modes/fail.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = failMode; 4 | 5 | function failMode(ctx) { 6 | var whiteList = getFailingTests(ctx.tapOutput); 7 | var env = {}; 8 | 9 | if (whiteList.length > 0) { 10 | env.ITAPE_NPM_TAPE_WHITELIST = JSON.stringify(whiteList); 11 | } 12 | ctx.setTestEnvironment(env); 13 | 14 | ctx.setStdoutFilter(function stdoutFilter(chunk) { 15 | chunk = String(chunk); 16 | if (chunk.indexOf('ok ') === 0) { 17 | return false; 18 | } 19 | return true; 20 | }); 21 | } 22 | 23 | function getFailingTests(tapOutput) { 24 | var fails = tapOutput.fail; 25 | 26 | var tests = uniq(fails.map(function getTests(fail) { 27 | return tapOutput.tests[fail.test - 1]; 28 | })).filter(Boolean); 29 | 30 | return tests.map(function toName(test) { 31 | return test.name; 32 | }); 33 | } 34 | 35 | function uniq(arr) { 36 | return arr.reduce(function check(acc, x) { 37 | if (acc.indexOf(x) === -1) { 38 | acc.push(x); 39 | } 40 | return acc; 41 | }, []); 42 | } 43 | -------------------------------------------------------------------------------- /modes/trace.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var $process = require('process'); 4 | 5 | module.exports = traceMode; 6 | 7 | function traceMode(ctx) { 8 | /*eslint complexity: [2, 15]*/ 9 | /*eslint max-statements: [2, 35]*/ 10 | var packageFile = ctx.packageFile; 11 | var packageJson = require(packageFile); 12 | 13 | var itapeConfig = packageJson.itape || {}; 14 | var traceConfig = itapeConfig.trace || {}; 15 | 16 | // null out traceConfig if not opted into trace 17 | if (!ctx.options.trace) { 18 | traceConfig = {}; 19 | } 20 | 21 | if (traceConfig.debuglog) { 22 | ctx.setTestEnvironment({ 23 | NODE_DEBUG: ($process.env.NODE_DEBUG || '') + ' ' + 24 | traceConfig.debuglog.join(' ') 25 | }); 26 | } 27 | 28 | if (traceConfig.leakedHandles || ctx.options.leakedHandles) { 29 | var leakedHandlesValue = configOrCli( 30 | traceConfig.leakedHandles, ctx.options.leakedHandles 31 | ); 32 | 33 | ctx.setTestEnvironment({ 34 | ITAPE_NPM_LEAKED_HANDLES: leakedHandlesValue 35 | }); 36 | } 37 | 38 | if (traceConfig.formatStack || ctx.options.formatStack) { 39 | if (ctx.options.formatStack === 'long') { 40 | ctx.options.formatStack = '{"traces":"long"}'; 41 | } 42 | 43 | var formatStackValue = configOrCli( 44 | traceConfig.formatStack, ctx.options.formatStack 45 | ); 46 | 47 | ctx.setTestEnvironment({ 48 | ITAPE_NPM_FORMAT_STACK: formatStackValue 49 | }); 50 | ctx.setCLIArg('color', true); 51 | } 52 | } 53 | 54 | function configOrCli(configValue, cliValue) { 55 | var jsonValue = null; 56 | if (configValue) { 57 | jsonValue = JSON.stringify( 58 | configValue 59 | ); 60 | } 61 | if (cliValue === true) { 62 | jsonValue = 'true'; 63 | } 64 | if (typeof cliValue === 'string') { 65 | jsonValue = cliValue; 66 | } 67 | 68 | return jsonValue; 69 | } 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "itape", 3 | "version": "1.10.0", 4 | "description": "Interactive tape test runner", 5 | "keywords": [], 6 | "author": "Raynos ", 7 | "repository": "git://github.com/Raynos/itape.git", 8 | "main": "index", 9 | "homepage": "https://github.com/Raynos/itape", 10 | "bugs": { 11 | "url": "https://github.com/Raynos/itape/issues", 12 | "email": "raynos2@gmail.com" 13 | }, 14 | "bin": { 15 | "itape": "./index.js" 16 | }, 17 | "man": "./man/itape.1", 18 | "contributors": [ 19 | { 20 | "name": "Raynos" 21 | } 22 | ], 23 | "dependencies": { 24 | "home-path": "^0.1.1", 25 | "minimist": "^1.1.0", 26 | "mkdirp": "^0.5.0", 27 | "parents": "^1.0.0", 28 | "process": "^0.10.0", 29 | "readable-stream": "^1.0.33", 30 | "resolve": "^1.0.0", 31 | "tap-out": "git://github.com/Raynos/tap-out#support-stack", 32 | "xtend": "^4.0.0" 33 | }, 34 | "devDependencies": { 35 | "format-stack": "^4.0.0", 36 | "jshint": "^2.5.0", 37 | "leaked-handles": "^5.2.0", 38 | "marked-man": "^0.1.4", 39 | "pre-commit": "0.0.5", 40 | "tap-spec": "^0.1.8" 41 | }, 42 | "licenses": [ 43 | { 44 | "type": "MIT", 45 | "url": "http://github.com/Raynos/itape/raw/master/LICENSE" 46 | } 47 | ], 48 | "scripts": { 49 | "man": "bash scripts/create-man.sh", 50 | "test": "npm run jshint -s", 51 | "unit-test": "NODE_ENV=test node test/index.js | tap-spec", 52 | "jshint-pre-commit": "jshint --verbose $(git diff --cached --name-only | grep '\\.js$')", 53 | "jshint": "jshint --verbose .", 54 | "cover": "istanbul cover --report none --print detail test/index.js", 55 | "view-cover": "istanbul report html && open ./coverage/index.html", 56 | "travis": "npm run cover -s && istanbul report lcov && ((cat coverage/lcov.info | coveralls) || exit 0)", 57 | "phantom": "run-browser test/index.js -b", 58 | "browser": "run-browser test/index.js" 59 | }, 60 | "engine": { 61 | "node": ">= 0.8.x" 62 | }, 63 | "pre-commit": [], 64 | "ngen-version": "4.0.3" 65 | } 66 | -------------------------------------------------------------------------------- /scripts/create-man.sh: -------------------------------------------------------------------------------- 1 | marked-man usage.md \ 2 | --version "v$(node -p "require('./package.json').version")" \ 3 | --manual "itape" \ 4 | > man/itape.1 5 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | 3 | var itape = require('../index.js'); 4 | 5 | test('itape is a function', function (assert) { 6 | assert.equal(typeof itape, 'function'); 7 | assert.end(); 8 | }); 9 | -------------------------------------------------------------------------------- /usage.md: -------------------------------------------------------------------------------- 1 | # itape(1) -- modal test runner 2 | 3 | ## SYNOPSIS 4 | 5 | `itape` [--help] [--fail] [--trace] [--debug] 6 | [--leaked-handles[=] [--format-stack[=]] 7 | [-h] [--color] 8 | 9 | ## DESCRIPTION 10 | 11 | `itape` is a modal test runner. It works exactly like `node` and 12 | is instrumented to support the `tape` testing framework. 13 | 14 | It supports multiple modes; a failure mode; a debugging mode 15 | and a tracing mode. 16 | 17 | ## EXAMPLES 18 | 19 | - `itape test/index.js` 20 | - `itape --fail test/index.js` 21 | - `itape --trace test/index.js` 22 | 23 | ## OPTIONS 24 | 25 | `--fail` 26 | Runs your tests in failure mode. `itape` will only run test 27 | blocks that failed last time. It also filters out all TAP 28 | noise and only shows `not ok`. 29 | 30 | `--trace` 31 | Runs your test in trace mode. Trace mode implies failure mode. 32 | Trace mode is defined by your package.json `"itape"` 33 | configuration. 34 | 35 | For example: 36 | 37 | ``` 38 | "itape": { 39 | "trace": { 40 | "debuglog": [ 41 | "typedrequesthandler" 42 | ], 43 | "leakedHandles": true, 44 | "formatStack": true 45 | } 46 | } 47 | ``` 48 | 49 | `--debug` 50 | Runs your tests in debug mode. Debug mode implies failure mode. 51 | When your program is running in debug mode we run your process 52 | with the node CLI debugger and set a break point on every 53 | failed assertion 54 | 55 | `--leaked-handles[=]` 56 | Runs your tests with the `leaked-handles` module included. 57 | 58 | You can pass a value to configure `leaked-handles` itself. 59 | For example: 60 | 61 | itape --leaked-handles='{"fullStack": true, "timeout": 1000, "debugSockets": true}' test/index.js 62 | 63 | `--format-stack[=]` 64 | Runs your tests with the `format-stack` module included. 65 | 66 | You can pass a value to configure `format-stack` itself. 67 | For example: 68 | 69 | itape --format-stack='{"traces":"long"}' test/index.js 70 | 71 | `--help` 72 | Displays help information 73 | 74 | `-h` 75 | Displays short help 76 | 77 | ## BUGS 78 | 79 | Please report any bugs to http://github.com/Raynos/itape 80 | 81 | ## LICENCE 82 | 83 | MIT Licenced 84 | 85 | ## SEE ALSO 86 | 87 | `tape(1), node.js(1)` 88 | --------------------------------------------------------------------------------