Line | Hits | Source |
---|---|---|
1 | /** | |
2 | * Primary export. | |
3 | */ | |
4 | ||
5 | 1 | module.exports = require('./nixt/runner'); |
6 | ||
7 | /** | |
8 | * Plugin support. | |
9 | */ | |
10 | ||
11 | 1 | module.exports.register = require('./nixt/plugin'); |
12 | ||
13 | /** | |
14 | * Module version. | |
15 | */ | |
16 | ||
17 | 1 | module.exports.version = require('../package.json').version; |
18 |
Line | Hits | Source |
---|---|---|
1 | /** | |
2 | * Batch - maintain the registered middlewares & expectations. | |
3 | * | |
4 | * `Batch` is being used by `Runner`. The main role of it is to | |
5 | * maintain the correct order of the registered middlewares and the expectations. | |
6 | * | |
7 | * In order to support "templates" `Batch` tries to encapsulate the mechanics | |
8 | * behind that. There are a few rules that one should keep in mind: | |
9 | * | |
10 | * - `addBefore` always adds at the end of the 'before' list | |
11 | * - `addAfter` always adds at the end of the 'after' list | |
12 | * - `add` adds either after the before list or before the after list depending if a "main" | |
13 | * function has been registered or not. | |
14 | * | |
15 | * The following example will (hopefully) illustrate how this class works: | |
16 | * | |
17 | * var batch = new Batch; | |
18 | * batch.addBefore(before1) --> execution order [before1] | |
19 | * batch.addBefore(before2) --> execution order [before1, before2] | |
20 | * batch.addAfter(after1) --> execution order [before1, before2, after1] | |
21 | * batch.add(fn1) --> execution order [before1, before2, fn1, after1] | |
22 | * batch.main(main) --> execution order [before1, before2, fn1, main, after1] | |
23 | * batch.add(fn2) --> execution order [before1, before2, fn1, main, fn2, after1] | |
24 | * batch.add(before3) --> execution order [before1, before2, before3, fn1, main, fn2, after1] | |
25 | * | |
26 | * | |
27 | * So why is this even useful? It's useful when you want to implement some sort of a template. | |
28 | * Imagine the following case - you always want to perform "setup" and "teardown" for some | |
29 | * app. In this particular case we'll discuss "todo" (npm install todo). Todo works with a simple | |
30 | * json file which happens to be its database. So if you were testing it you would want to start | |
31 | * with a clean state each and every time. Here is how you could accomplish that: | |
32 | * | |
33 | * var todo = nixt() | |
34 | * .before(createBlankDatabase); | |
35 | * .after(removeTheDatabase); | |
36 | * | |
37 | * Now you can put this in a helper function for your tests: | |
38 | * | |
39 | * function todoApp() { | |
40 | * return todo.clone(); | |
41 | * } | |
42 | * | |
43 | * And now every time when you need to create a new instance you can do so by calling the simple | |
44 | * helper method that you have created. Of course there are many ways to accomplish the same, but | |
45 | * nixt gives you the ability to keep everything simple. | |
46 | * | |
47 | * @constructor | |
48 | */ | |
49 | ||
50 | function Batch() { | |
51 | 4 | this.before = []; |
52 | 4 | this.afterBefore = []; |
53 | 4 | this.after = []; |
54 | 4 | this.beforeAfter = []; |
55 | 4 | this.fn = null; |
56 | } | |
57 | ||
58 | /** | |
59 | * Push `fn` into the before list. | |
60 | * | |
61 | * @param {Function} fn | |
62 | * @api public | |
63 | */ | |
64 | ||
65 | 1 | Batch.prototype.addBefore = function(fn) { |
66 | 2 | this.before.push(fn); |
67 | }; | |
68 | ||
69 | /** | |
70 | * Push `fn` into the after list. | |
71 | * | |
72 | * @param {Function} fn | |
73 | * @api public | |
74 | */ | |
75 | ||
76 | 1 | Batch.prototype.addAfter = function(fn) { |
77 | 5 | this.after.push(fn); |
78 | }; | |
79 | ||
80 | /** | |
81 | * Register a function in either the "after before" list | |
82 | * or in the "before after" list, depending if a "main" | |
83 | * function exists. | |
84 | * | |
85 | * @see Batch#hasMain | |
86 | * @see Batch#main | |
87 | * @param {Function} fn | |
88 | * @api public | |
89 | */ | |
90 | ||
91 | 1 | Batch.prototype.add = function(fn) { |
92 | 18 | (this.hasMain() ? this.beforeAfter : this.afterBefore).push(fn); |
93 | }; | |
94 | ||
95 | /** | |
96 | * Register a "main" function. | |
97 | * | |
98 | * @param {Function} fn | |
99 | * @api public | |
100 | */ | |
101 | ||
102 | 1 | Batch.prototype.main = function(fn) { |
103 | 27 | this.fn = fn; |
104 | }; | |
105 | ||
106 | /** | |
107 | * Return if there is a main function or not. | |
108 | * | |
109 | * @returns {Boolean} | |
110 | * @api public | |
111 | */ | |
112 | ||
113 | 1 | Batch.prototype.hasMain = function() { |
114 | 46 | return !!this.fn; |
115 | }; | |
116 | ||
117 | /** | |
118 | * Execute all registered functions. Keep in mind that the result of | |
119 | * the "main" function will be supplied to the last callback. | |
120 | * | |
121 | * @param {Function} last fn to execute | |
122 | * @api public | |
123 | */ | |
124 | ||
125 | 1 | Batch.prototype.run = function(fn) { |
126 | 27 | var err = null; |
127 | 27 | var main = this.fn; |
128 | 27 | var batch = this.before.slice(0).concat(this.afterBefore); |
129 | ||
130 | 27 | batch.push(function(next) { |
131 | 81 | main(function(e) { err = e; next(); }); |
132 | }); | |
133 | ||
134 | 27 | batch = batch.concat(this.beforeAfter).concat(this.after); |
135 | ||
136 | 27 | batch.push(function() { |
137 | 27 | fn(err); |
138 | }); | |
139 | ||
140 | function next() { | |
141 | 106 | var fn = batch.shift(); |
142 | 133 | if (!fn) return; |
143 | 125 | if (fn.length) return fn(next); |
144 | 33 | fn(); |
145 | 33 | next(); |
146 | } | |
147 | ||
148 | 27 | next(); |
149 | }; | |
150 | ||
151 | /** | |
152 | * Primary exports. | |
153 | */ | |
154 | ||
155 | 1 | module.exports = Batch; |
156 |
Line | Hits | Source |
---|---|---|
1 | /** | |
2 | * Core dependencies. | |
3 | */ | |
4 | ||
5 | 1 | var exec = require('child_process').exec; |
6 | ||
7 | /** | |
8 | * Internal dependencies. | |
9 | */ | |
10 | ||
11 | 1 | var Result = require('./result'); |
12 | ||
13 | /** | |
14 | * Command to execute. | |
15 | * | |
16 | * This class is a simple wrapper of `child_process#exec`. All it does is | |
17 | * executing the command with the supplied options and then return a new | |
18 | * `Result`. It delegates the formatting of stdout and stderr to `Formatter`. | |
19 | * | |
20 | * @param {Formatter} formatter | |
21 | * @see child_process#exec | |
22 | * @see Formatter | |
23 | * @see Result | |
24 | * @constructor | |
25 | */ | |
26 | ||
27 | function Command(formatter) { | |
28 | 3 | this.formatter = formatter; |
29 | } | |
30 | ||
31 | /** | |
32 | * Set a command to be executed. | |
33 | * | |
34 | * @param {String} command | |
35 | * @api public | |
36 | */ | |
37 | ||
38 | 1 | Command.prototype.set = function(cmd) { |
39 | 27 | this.cmd = cmd; |
40 | }; | |
41 | ||
42 | /** | |
43 | * Set a command timeout. | |
44 | * | |
45 | * @param {Number} | |
46 | * @api public | |
47 | */ | |
48 | ||
49 | 1 | Command.prototype.timeout = function(ms) { |
50 | 1 | this.ms = ms; |
51 | }; | |
52 | ||
53 | /** | |
54 | * Set the current working directory for | |
55 | * the command. | |
56 | * | |
57 | * @param {String} path | |
58 | * @api public | |
59 | */ | |
60 | ||
61 | 1 | Command.prototype.cwd = function(cwd) { |
62 | 3 | this.dir = cwd; |
63 | }; | |
64 | ||
65 | /** | |
66 | * Execute the command, load stdout and stderr into | |
67 | * the formatter and return a new `Result`. | |
68 | * | |
69 | * @param {Function} fn | |
70 | * @api public | |
71 | */ | |
72 | ||
73 | 1 | Command.prototype.exec = function(fn) { |
74 | 27 | var self = this; |
75 | 27 | var options = { cwd: this.dir, timeout: this.ms }; |
76 | ||
77 | 27 | exec(this.cmd, options, function(err, stdout, stderr) { |
78 | 27 | self.formatter.load(stdout, stderr); |
79 | 27 | fn(new Result(self.formatter, err, self.cmd)); |
80 | }); | |
81 | }; | |
82 | ||
83 | /** | |
84 | * Primary export. | |
85 | */ | |
86 | ||
87 | 1 | module.exports = Command; |
88 |
Line | Hits | Source |
---|---|---|
1 | /** | |
2 | * Core dependencies. | |
3 | */ | |
4 | ||
5 | 1 | var fs = require('fs'); |
6 | ||
7 | /** | |
8 | * External dependencies. | |
9 | */ | |
10 | ||
11 | 1 | var AssertionError = require('assertion-error'); |
12 | ||
13 | /** | |
14 | * Return an exit code expectation. | |
15 | * | |
16 | * @param {Number} expected exit code. | |
17 | * @returns {Function} | |
18 | * @api public | |
19 | */ | |
20 | ||
21 | 1 | exports.code = function(code) { |
22 | 3 | return function(result) { |
23 | 3 | if (code !== result.code) { |
24 | 1 | return error(result, 'Expected exit code: "' + code + '", actual: "' + result.code + '"'); |
25 | } | |
26 | }; | |
27 | }; | |
28 | ||
29 | /** | |
30 | * Return no timeout expectation. | |
31 | * | |
32 | * @returns {Function} | |
33 | * @api public | |
34 | */ | |
35 | ||
36 | 1 | exports.time = function() { |
37 | 1 | return function(result) { |
38 | 1 | if (result.killed) { |
39 | 1 | return error(result, 'Command execution terminated (timeout)'); |
40 | } | |
41 | }; | |
42 | }; | |
43 | ||
44 | /** | |
45 | * Return a stderr expectation. | |
46 | * | |
47 | * @param {String|RegExp} expected string or regular express to match | |
48 | * @returns {Function} | |
49 | * @api public | |
50 | */ | |
51 | ||
52 | 1 | exports.stderr = function(expected) { |
53 | 6 | return function(result) { |
54 | 6 | return assertOut('stderr', expected, result); |
55 | }; | |
56 | }; | |
57 | ||
58 | /** | |
59 | * Return a stdout expectation. | |
60 | * | |
61 | * @param {String|RegExp} expected string or regular express to match | |
62 | * @returns {Function} | |
63 | * @api public | |
64 | */ | |
65 | ||
66 | 1 | exports.stdout = function(expected) { |
67 | 12 | return function(result) { |
68 | 12 | return assertOut('stdout', expected, result); |
69 | }; | |
70 | }; | |
71 | ||
72 | /** | |
73 | * Verify that a `path` exists. | |
74 | * | |
75 | * @param {String} path | |
76 | * @returns {Function} | |
77 | * @api public | |
78 | */ | |
79 | ||
80 | 1 | exports.exists = function(path) { |
81 | 4 | return function(result) { |
82 | 4 | if (fs.existsSync(path) !== true) { |
83 | 2 | return error(result, 'Expected "' + path + '" to exist.'); |
84 | } | |
85 | }; | |
86 | }; | |
87 | ||
88 | /** | |
89 | * Verify that `path`'s data matches `data`. | |
90 | * | |
91 | * @param {String} path | |
92 | * @param {String|RegExp} data | |
93 | * @returns {Function} | |
94 | * @api public | |
95 | */ | |
96 | ||
97 | 1 | exports.match = function(path, data) { |
98 | 3 | return function(result) { |
99 | 3 | var contents = fs.readFileSync(path, { encoding: 'utf8' }); |
100 | 3 | var statement = data instanceof RegExp |
101 | ? data.test(contents) | |
102 | : data === contents; | |
103 | ||
104 | 3 | if (statement !== true) { |
105 | 1 | return error(result, 'Expected "' + path + '" to match "' + data + '", but it was: "' + contents + '"'); |
106 | } | |
107 | }; | |
108 | }; | |
109 | ||
110 | /** | |
111 | * Assert stdout or stderr. | |
112 | * | |
113 | * @param {String} stdout/stderr | |
114 | * @param {Mixed} expected | |
115 | * @param {Result} result | |
116 | * @returns {AssertionError|null} | |
117 | * @api private | |
118 | */ | |
119 | ||
120 | function assertOut(key, expected, result) { | |
121 | 18 | var actual = result[key]; |
122 | 18 | var statement = expected instanceof RegExp |
123 | ? expected.test(actual) | |
124 | : expected === actual; | |
125 | ||
126 | 18 | if (statement !== true) { |
127 | 4 | var message = 'Expected ' + key +' to match "' + expected + '". Actual: "' + actual + '"'; |
128 | 4 | return error(result, message); |
129 | } | |
130 | } | |
131 | ||
132 | /** | |
133 | * Create and return a new `AssertionError`. | |
134 | * It will assign the given `result` to it, it will also prepend the executed command | |
135 | * to the error message. | |
136 | * | |
137 | * Assertion error is a constructor for test and validation frameworks that implements | |
138 | * standardized Assertion Error specification. | |
139 | * | |
140 | * For more info go visit https://github.com/chaijs/assertion-error | |
141 | * | |
142 | * @param {Result} result | |
143 | * @param {String} error message | |
144 | * @returns {AssertionError} | |
145 | * @api private | |
146 | */ | |
147 | ||
148 | function error(result, message) { | |
149 | 9 | var err = new AssertionError('`' + result.cmd + '`: ' + message); |
150 | 9 | err.result = result; |
151 | 9 | return err; |
152 | } | |
153 |
Line | Hits | Source |
---|---|---|
1 | /** | |
2 | * Command-line response formatter. | |
3 | * | |
4 | * Options: | |
5 | * | |
6 | * - colors Leave colors, default: true | |
7 | * - newlines Leave newlines, default: true | |
8 | * | |
9 | * Notes: | |
10 | * | |
11 | * - Probably it doesn't make a lot of sense to store the result into | |
12 | * the given instance. It could be just a simple helper, however | |
13 | * by using the current design the communication between `Runner` | |
14 | * `Command` and `Result` is quite simplified. | |
15 | * | |
16 | * @param {Object} options | |
17 | * @constructor | |
18 | */ | |
19 | ||
20 | function Formatter(options) { | |
21 | 4 | options = options || {}; |
22 | 4 | this.colors = options.colors; |
23 | 4 | this.newlines = options.newlines; |
24 | } | |
25 | ||
26 | /** | |
27 | * Format the command-line result. | |
28 | * | |
29 | * @param {String} stdout | |
30 | * @param {String} stderr | |
31 | * @api public | |
32 | */ | |
33 | ||
34 | 1 | Formatter.prototype.load = function(stdout, stderr) { |
35 | 27 | this.stdout = this.strip(stdout); |
36 | 27 | this.stderr = this.strip(stderr); |
37 | }; | |
38 | ||
39 | /** | |
40 | * `Formatter#strip` will do the following: | |
41 | * | |
42 | * - Remove the last new line symbol from the string (always) | |
43 | * - Strip new lines (optional, see `options`) | |
44 | * - Strip colors (optional, see `options`) | |
45 | * | |
46 | * Acknowledgments: | |
47 | * | |
48 | * - StripColorCodes - MIT License | |
49 | * | |
50 | * @param {String} str | |
51 | * @returns {String} | |
52 | * @api private | |
53 | */ | |
54 | ||
55 | 1 | Formatter.prototype.strip = function(str) { |
56 | 54 | str = str.replace(/\r?\n|\r$/, ''); |
57 | ||
58 | 54 | if (this.newlines === false) { |
59 | 2 | str = str.replace(/\r?\n|\r/g, ''); |
60 | } | |
61 | ||
62 | 54 | if (this.colors === false) { |
63 | 2 | str = str.replace(/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]/g, ''); |
64 | } | |
65 | ||
66 | 54 | return str; |
67 | }; | |
68 | ||
69 | /** | |
70 | * Primary exports. | |
71 | */ | |
72 | ||
73 | 1 | module.exports = Formatter; |
74 |
Line | Hits | Source |
---|---|---|
1 | /** | |
2 | * Core dependencies. | |
3 | */ | |
4 | ||
5 | 1 | var exec = require('child_process').exec; |
6 | 1 | var fs = require('fs'); |
7 | ||
8 | /** | |
9 | * Asynchronous mkdir(2). | |
10 | * | |
11 | * @param {String} path | |
12 | * @returns {Function} middleware | |
13 | * @see fs#mkdir | |
14 | * @api public | |
15 | */ | |
16 | ||
17 | 1 | exports.mkdir = function(path) { |
18 | 3 | return function(next) { |
19 | 3 | fs.mkdir(path, done(next)); |
20 | }; | |
21 | }; | |
22 | ||
23 | /** | |
24 | * Asynchronously writes data to a file, replacing the file if it already exists. | |
25 | * `data` can be a string or a buffer. | |
26 | * | |
27 | * @param {String} path | |
28 | * @param {Buffer|String} data | |
29 | * @returns {Function} middleware | |
30 | * @see fs#writeFile | |
31 | * @api public | |
32 | */ | |
33 | ||
34 | 1 | exports.writeFile = function(path, data) { |
35 | 6 | return function(next) { |
36 | 6 | fs.writeFile(path, data, done(next)); |
37 | }; | |
38 | }; | |
39 | ||
40 | /** | |
41 | * Asynchronous rmdir(2). | |
42 | * | |
43 | * @param {String} path | |
44 | * @returns {Function} middleware | |
45 | * @see fs#rmdir | |
46 | * @api public | |
47 | */ | |
48 | ||
49 | 1 | exports.rmdir = function(path) { |
50 | 2 | return function(next) { |
51 | 2 | fs.rmdir(path, done(next)); |
52 | }; | |
53 | }; | |
54 | ||
55 | /** | |
56 | * Asynchronous unlink(2). | |
57 | * | |
58 | * @param {String} path | |
59 | * @returns {Function} middleware | |
60 | * @see fs#unlink | |
61 | * @api public | |
62 | */ | |
63 | ||
64 | 1 | exports.unlink = function(path) { |
65 | 6 | return function(next) { |
66 | 6 | fs.unlink(path, done(next)); |
67 | }; | |
68 | }; | |
69 | ||
70 | /** | |
71 | * Run a command in a shell. | |
72 | * | |
73 | * @param {String} the command to run | |
74 | * @returns {Function} middleware | |
75 | * @see child_process#exec | |
76 | * @api public | |
77 | */ | |
78 | ||
79 | 1 | exports.exec = function(cmd) { |
80 | 1 | return function(next) { |
81 | 1 | exec(cmd, next); |
82 | }; | |
83 | }; | |
84 | ||
85 | /** | |
86 | * Callback generator for middlewares. Throw errors if any. | |
87 | * | |
88 | * @param {Function} next | |
89 | * @returns {Function} | |
90 | * @api public | |
91 | */ | |
92 | ||
93 | function done(next) { | |
94 | 17 | return function(err) { |
95 | 17 | if (err) throw err; |
96 | 17 | next(); |
97 | }; | |
98 | } | |
99 |
Line | Hits | Source |
---|---|---|
1 | /** | |
2 | * Internal dependencies. | |
3 | */ | |
4 | ||
5 | 1 | var Runner = require('./runner'); |
6 | ||
7 | /** | |
8 | * Primitive plugin support. | |
9 | * | |
10 | * It will add the supplied `fn to Runner's prototype. | |
11 | * | |
12 | * Examples: | |
13 | * | |
14 | * Register a single function, could be both middleware or expectation: | |
15 | * | |
16 | * nixt.register('stdoutNotEqual', fn); | |
17 | * | |
18 | * Later on this can be used as you would expect: | |
19 | * | |
20 | * nixt() | |
21 | * .run('ls /tmp') | |
22 | * .stdoutNotEqual('xxx') | |
23 | * .end() | |
24 | * | |
25 | * In case you want to register more than one function at once you may want to pass | |
26 | * an object: | |
27 | * | |
28 | * nixt.register({ | |
29 | * name: fn, | |
30 | * otherName: fn2, | |
31 | * etc: etc, | |
32 | * }); | |
33 | * | |
34 | * The second example might come handy when developing plugins. Keep in mind that | |
35 | * the plugin system will most certainly change in future version (prior hitting 1.0.0). | |
36 | * The current implementation has some obvious problems like what plugin developers | |
37 | * will do if they happen to use the same function name. Any ideas and suggestions | |
38 | * are more than welcome. | |
39 | * | |
40 | * @param {String|Object} name | |
41 | * @param {Function} fn | |
42 | * @api public | |
43 | */ | |
44 | ||
45 | 1 | module.exports = function(name, fn) { |
46 | 2 | var reg = null; |
47 | ||
48 | 2 | if (Object(name) !== name) { |
49 | 1 | reg = Object.create(null); |
50 | 1 | reg[name] = fn; |
51 | } else { | |
52 | 1 | reg = name; |
53 | } | |
54 | ||
55 | 2 | Object.keys(reg).forEach(function(key) { |
56 | 3 | Runner.prototype[key] = reg[key]; |
57 | }); | |
58 | }; | |
59 |
Line | Hits | Source |
---|---|---|
1 | /** | |
2 | * Simple value object that contains the result of | |
3 | * `Command`. | |
4 | * | |
5 | * @see Command | |
6 | * @constructor | |
7 | */ | |
8 | ||
9 | function Result(formatter, err, cmd) { | |
10 | 27 | this.err = err; |
11 | 27 | this.cmd = cmd; |
12 | 27 | this.stdout = formatter.stdout; |
13 | 27 | this.stderr = formatter.stderr; |
14 | 27 | this.code = err ? err.code : 0; |
15 | 27 | this.killed = err && err.killed; |
16 | } | |
17 | ||
18 | /** | |
19 | * Primary export. | |
20 | */ | |
21 | ||
22 | 1 | module.exports = Result; |
23 |
Line | Hits | Source |
---|---|---|
1 | /** | |
2 | * External dependencies. | |
3 | */ | |
4 | ||
5 | 1 | var clone = require('clone'); |
6 | ||
7 | /** | |
8 | * Internal dependencies. | |
9 | */ | |
10 | ||
11 | 1 | var Batch = require('./batch'); |
12 | 1 | var Command = require('./command'); |
13 | 1 | var Formatter = require('./formatter'); |
14 | 1 | var expect = require('./expectations'); |
15 | 1 | var middlewares = require('./middlewares'); |
16 | ||
17 | /** | |
18 | * The primary entry point for every Nixt test. | |
19 | * It provides public interface that the users will interact with. | |
20 | * Every `Runner` instance can be cloned and this way one can build | |
21 | * the so called "templates". | |
22 | * | |
23 | * Options: | |
24 | * | |
25 | * - colors: default - true, Strip colors from stdout and stderr when `false` | |
26 | * - newlines: default - true, Strip new lines from stdout and stderr when `false` | |
27 | * | |
28 | * Examples: | |
29 | * | |
30 | * Instantiating the class: | |
31 | * | |
32 | * nixt() // -> Runner | |
33 | * new nixt // -> Runner | |
34 | * | |
35 | * Simple stdout assertion: | |
36 | * | |
37 | * nixt({ colors: false, newlines: false }) | |
38 | * .exec('todo clear') | |
39 | * .exec('todo Buy milk') | |
40 | * .run('todo ls') | |
41 | * .stdout('Buy milk') | |
42 | * .end(fn); | |
43 | * | |
44 | * Stdout assertion: | |
45 | * | |
46 | * nixt({ colors: false, newlines: false }) | |
47 | * .exec('todo clear') | |
48 | * .run('todo') | |
49 | * .stderr('Please enter a todo') | |
50 | * .end(fn); | |
51 | * | |
52 | * So repeating "todo clear" is simply ugly. You can avoid this by | |
53 | * creating a "template". | |
54 | * | |
55 | * var todo = nixt().before(clearTodos); | |
56 | * | |
57 | * Later on: | |
58 | * | |
59 | * todo.clone().exec... | |
60 | * | |
61 | * For more examples check the "README" file. | |
62 | * | |
63 | * @see Batch | |
64 | * @see Formatter | |
65 | * @see Command | |
66 | * @param {Object} options | |
67 | * @constructor | |
68 | */ | |
69 | ||
70 | function Runner(options) { | |
71 | 12 | if (!(this instanceof Runner)) return new Runner(options); |
72 | 4 | options = options || {}; |
73 | 4 | this.options = options; |
74 | 4 | this.formatter = new Formatter({ colors: options.colors, newlines: options.newlines }); |
75 | 4 | this.expectations = []; |
76 | 4 | this.batch = new Batch; |
77 | 4 | this.baseCmd = ''; |
78 | } | |
79 | ||
80 | /** | |
81 | * Register a before filter. | |
82 | * | |
83 | * @param {Function} fn | |
84 | * @returns {Runner} for chaining | |
85 | * @see Batch#addBefore | |
86 | * @api public | |
87 | */ | |
88 | ||
89 | 1 | Runner.prototype.before = function(fn) { |
90 | 2 | this.batch.addBefore(fn); |
91 | 2 | return this; |
92 | }; | |
93 | ||
94 | /** | |
95 | * Register an after filter. | |
96 | * | |
97 | * @param {Function} fn | |
98 | * @returns {Runner} for chaining | |
99 | * @see Batch#addAfter | |
100 | * @api public | |
101 | */ | |
102 | ||
103 | 1 | Runner.prototype.after = function(fn) { |
104 | 5 | this.batch.addAfter(fn); |
105 | 5 | return this; |
106 | }; | |
107 | ||
108 | /** | |
109 | * Set the current working directory for | |
110 | * the command that will be executed. | |
111 | * | |
112 | * @param {String} path | |
113 | * @returns {Runner} for chaining | |
114 | * @api public | |
115 | */ | |
116 | ||
117 | 1 | Runner.prototype.cwd = function(path) { |
118 | 3 | this.command().cwd(path); |
119 | 3 | return this; |
120 | }; | |
121 | ||
122 | /** | |
123 | * Specify a base command. | |
124 | * | |
125 | * Very convenient when testing the same executable | |
126 | * again and again. | |
127 | * | |
128 | * @param {String} command | |
129 | * @returns {Runner} for chaining | |
130 | * @api public | |
131 | */ | |
132 | ||
133 | 1 | Runner.prototype.base = function(cmd) { |
134 | 1 | this.baseCmd = cmd; |
135 | 1 | return this; |
136 | }; | |
137 | ||
138 | /** | |
139 | * Specify a command to run. | |
140 | * | |
141 | * @param {String} command | |
142 | * @returns {Runner} for chaining | |
143 | * @see Batch#main | |
144 | * @api public | |
145 | */ | |
146 | ||
147 | 1 | Runner.prototype.run = function(cmd, fn) { |
148 | 27 | this.command().set(this.baseCmd + cmd); |
149 | 27 | this.batch.main(this.execFn()); |
150 | 27 | if (fn) this.end(fn); |
151 | 27 | return this; |
152 | }; | |
153 | ||
154 | /** | |
155 | * Force an execution timeout. | |
156 | * | |
157 | * @param {Number} ms | |
158 | * @returns {Runner} for chaining | |
159 | * @api public | |
160 | */ | |
161 | ||
162 | 1 | Runner.prototype.timeout = function(ms) { |
163 | 1 | this.command().timeout(ms); |
164 | 1 | this.expect(expect.time(ms)); |
165 | 1 | return this; |
166 | }; | |
167 | ||
168 | /** | |
169 | * Register a "stdout" expectation. | |
170 | * | |
171 | * @param {Regex|String} pattern | |
172 | * @returns {Runner} for chaining | |
173 | * @api public | |
174 | */ | |
175 | ||
176 | 1 | Runner.prototype.stdout = function(pattern) { |
177 | 12 | this.expect(expect.stdout(pattern)); |
178 | 12 | return this; |
179 | }; | |
180 | ||
181 | /** | |
182 | * Register a "stderr" expectation. | |
183 | * | |
184 | * @param {Regex|String} pattern | |
185 | * @returns {Runner} for chaining | |
186 | * @api public | |
187 | */ | |
188 | ||
189 | 1 | Runner.prototype.stderr = function(pattern) { |
190 | 6 | this.expect(expect.stderr(pattern)); |
191 | 6 | return this; |
192 | }; | |
193 | ||
194 | /** | |
195 | * Register an exit code expectation. | |
196 | * | |
197 | * @param {Number} code | |
198 | * @returns {Runner} for chaining | |
199 | * @api public | |
200 | */ | |
201 | ||
202 | 1 | Runner.prototype.code = function(code) { |
203 | 3 | this.expect(expect.code(code)); |
204 | 3 | return this; |
205 | }; | |
206 | ||
207 | /** | |
208 | * Check if a file or a directory exists. | |
209 | * | |
210 | * @param {String} path | |
211 | * @returns {Runner} for chaining | |
212 | * @api public | |
213 | */ | |
214 | ||
215 | 1 | Runner.prototype.exist = function(path) { |
216 | 4 | this.expect(expect.exists(path)); |
217 | 4 | return this; |
218 | }; | |
219 | ||
220 | /** | |
221 | * Match the content of a file. | |
222 | * | |
223 | * @param {Regex|String} pattern | |
224 | * @returns {Runner} for chaining | |
225 | * @api public | |
226 | */ | |
227 | ||
228 | 1 | Runner.prototype.match = function(file, pattern) { |
229 | 3 | this.expect(expect.match(file, pattern)); |
230 | 3 | return this; |
231 | }; | |
232 | ||
233 | /** | |
234 | * Create a new directory. | |
235 | * | |
236 | * @param {String} path | |
237 | * @returns {Runner} for chaining | |
238 | * @api public | |
239 | */ | |
240 | ||
241 | 1 | Runner.prototype.mkdir = function(path) { |
242 | 3 | this.batch.add(middlewares.mkdir(path)); |
243 | 3 | return this; |
244 | }; | |
245 | ||
246 | /** | |
247 | * Execute a command. | |
248 | * | |
249 | * @param {String} command | |
250 | * @returns {Runner} for chaining | |
251 | * @api public | |
252 | */ | |
253 | ||
254 | 1 | Runner.prototype.exec = function(cmd) { |
255 | 1 | this.batch.add(middlewares.exec(cmd)); |
256 | 1 | return this; |
257 | }; | |
258 | ||
259 | /** | |
260 | * Create a new file with the given `content`. | |
261 | * | |
262 | * @param {String} path | |
263 | * @param {String} data [optional] | |
264 | * @returns {Runner} for chaining | |
265 | * @api public | |
266 | */ | |
267 | ||
268 | 1 | Runner.prototype.writeFile = function(path, data) { |
269 | 6 | this.batch.add(middlewares.writeFile(path, data)); |
270 | 6 | return this; |
271 | }; | |
272 | ||
273 | /** | |
274 | * Remove a directory. | |
275 | * | |
276 | * @param {String} path | |
277 | * @returns {Runner} for chaining | |
278 | * @api public | |
279 | */ | |
280 | ||
281 | 1 | Runner.prototype.rmdir = function(path) { |
282 | 2 | this.batch.add(middlewares.rmdir(path)); |
283 | 2 | return this; |
284 | }; | |
285 | ||
286 | /** | |
287 | * Remove a file. | |
288 | * | |
289 | * @param {String} path | |
290 | * @returns {Runner} for chaining | |
291 | * @api public | |
292 | */ | |
293 | ||
294 | 1 | Runner.prototype.unlink = function(path) { |
295 | 6 | this.batch.add(middlewares.unlink(path)); |
296 | 6 | return this; |
297 | }; | |
298 | ||
299 | /** | |
300 | * Run the test. | |
301 | * | |
302 | * @param {Function} fn | |
303 | * @returns {Runner} for chaining | |
304 | * @api public | |
305 | */ | |
306 | ||
307 | 1 | Runner.prototype.end = function(fn) { |
308 | 29 | if (!this.batch.hasMain()) throw new Error('Please provide a command to run. Hint: `nixt#run`'); |
309 | 27 | this.batch.run(fn); |
310 | }; | |
311 | ||
312 | /** | |
313 | * Clone the runner. Give basic support for templates. | |
314 | * | |
315 | * @returns {Runner} clone of the current instance | |
316 | * @api public | |
317 | */ | |
318 | ||
319 | 1 | Runner.prototype.clone = function() { |
320 | 25 | return clone(this, false); |
321 | }; | |
322 | ||
323 | /** | |
324 | * Register an expectation. | |
325 | * | |
326 | * @param {Function} fn | |
327 | * @api public | |
328 | */ | |
329 | ||
330 | 1 | Runner.prototype.expect = function(fn) { |
331 | 29 | this.expectations.push(fn); |
332 | 29 | return this; |
333 | }; | |
334 | ||
335 | /** | |
336 | * Command factory. | |
337 | * | |
338 | * Return the current command (or create a new one | |
339 | * if none exists yet). | |
340 | * | |
341 | * @returns {Command} | |
342 | * @api private | |
343 | */ | |
344 | ||
345 | 1 | Runner.prototype.command = function() { |
346 | 58 | this._command = this._command || new Command(this.formatter); |
347 | 58 | return this._command; |
348 | }; | |
349 | ||
350 | /** | |
351 | * Return a function that will execute | |
352 | * the command. | |
353 | * | |
354 | * @returns {Function} | |
355 | * @api private | |
356 | */ | |
357 | ||
358 | 1 | Runner.prototype.execFn = function() { |
359 | 27 | var self = this; |
360 | ||
361 | 27 | return function(fn) { |
362 | 27 | self.command().exec(function(result) { |
363 | 27 | var err = null; |
364 | 27 | for (var i = 0, len = self.expectations.length; i < len; i++) { |
365 | 29 | err = self.expectations[i](result); |
366 | 38 | if (err) break; |
367 | } | |
368 | 27 | fn(err); |
369 | }); | |
370 | }; | |
371 | }; | |
372 | ||
373 | /** | |
374 | * Primary export. | |
375 | */ | |
376 | ||
377 | 1 | module.exports = Runner; |
378 |