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 | 28 | 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 | 47 | 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 | 28 | var err = null; |
127 | 28 | var main = this.fn; |
128 | 28 | var batch = this.before.slice(0).concat(this.afterBefore); |
129 | ||
130 | 28 | batch.push(function(next) { |
131 | 84 | main(function(e) { err = e; next(); }); |
132 | }); | |
133 | ||
134 | 28 | batch = batch.concat(this.beforeAfter).concat(this.after); |
135 | ||
136 | 28 | batch.push(function() { |
137 | 28 | fn(err); |
138 | }); | |
139 | ||
140 | function next() { | |
141 | 109 | var fn = batch.shift(); |
142 | 137 | if (!fn) return; |
143 | 128 | if (fn.length) return fn(next); |
144 | 34 | fn(); |
145 | 34 | next(); |
146 | } | |
147 | ||
148 | 28 | 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 | 3 | this.envs = {}; |
30 | } | |
31 | ||
32 | /** | |
33 | * Set a command to be executed. | |
34 | * | |
35 | * @param {String} command | |
36 | * @api public | |
37 | */ | |
38 | ||
39 | 1 | Command.prototype.set = function(cmd) { |
40 | 28 | this.cmd = cmd; |
41 | }; | |
42 | ||
43 | /** | |
44 | * Set a command timeout. | |
45 | * | |
46 | * @param {Number} | |
47 | * @api public | |
48 | */ | |
49 | ||
50 | 1 | Command.prototype.timeout = function(ms) { |
51 | 1 | this.ms = ms; |
52 | }; | |
53 | ||
54 | /** | |
55 | * Set the current working directory for | |
56 | * the command. | |
57 | * | |
58 | * @param {String} path | |
59 | * @api public | |
60 | */ | |
61 | ||
62 | 1 | Command.prototype.cwd = function(cwd) { |
63 | 3 | this.dir = cwd; |
64 | }; | |
65 | ||
66 | /** | |
67 | * Set environemnt variable. | |
68 | * | |
69 | * @param {String} key | |
70 | * @param {String} val | |
71 | * @api public | |
72 | */ | |
73 | ||
74 | 1 | Command.prototype.env = function(key, val) { |
75 | 2 | this.envs[key] = val; |
76 | }; | |
77 | ||
78 | /** | |
79 | * Execute the command, load stdout and stderr into | |
80 | * the formatter and return a new `Result`. | |
81 | * | |
82 | * @param {Function} fn | |
83 | * @api public | |
84 | */ | |
85 | ||
86 | 1 | Command.prototype.exec = function(fn) { |
87 | 28 | var self = this; |
88 | 28 | var options = { cwd: this.dir, timeout: this.ms, env: this.envs }; |
89 | ||
90 | 28 | exec(this.cmd, options, function(err, stdout, stderr) { |
91 | 28 | self.formatter.load(stdout, stderr); |
92 | 28 | fn(new Result(self.formatter, err, self.cmd)); |
93 | }); | |
94 | }; | |
95 | ||
96 | /** | |
97 | * Primary export. | |
98 | */ | |
99 | ||
100 | 1 | module.exports = Command; |
101 |
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 | 4 | return function(result) { |
23 | 4 | 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 | 28 | this.stdout = this.strip(stdout); |
36 | 28 | 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 | 56 | str = str.replace(/\r?\n|\r$/, ''); |
57 | ||
58 | 56 | if (this.newlines === false) { |
59 | 2 | str = str.replace(/\r?\n|\r/g, ''); |
60 | } | |
61 | ||
62 | 56 | if (this.colors === false) { |
63 | 2 | str = str.replace(/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]/g, ''); |
64 | } | |
65 | ||
66 | 56 | 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 | 28 | this.err = err; |
11 | 28 | this.cmd = cmd; |
12 | 28 | this.stdout = formatter.stdout; |
13 | 28 | this.stderr = formatter.stderr; |
14 | 28 | this.code = err ? err.code : 0; |
15 | 28 | 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 | * Set environment variable. | |
140 | * | |
141 | * @param {String} key | |
142 | * @param {String} value | |
143 | * @returns {Runner} for chaining | |
144 | * @see Command#env | |
145 | * @api public | |
146 | */ | |
147 | ||
148 | 1 | Runner.prototype.env = function(key, val) { |
149 | 2 | this.command().env(key, val); |
150 | 2 | return this; |
151 | }; | |
152 | ||
153 | /** | |
154 | * Specify a command to run. | |
155 | * | |
156 | * @param {String} command | |
157 | * @returns {Runner} for chaining | |
158 | * @see Batch#main | |
159 | * @api public | |
160 | */ | |
161 | ||
162 | 1 | Runner.prototype.run = function(cmd, fn) { |
163 | 28 | this.command().set(this.baseCmd + cmd); |
164 | 28 | this.batch.main(this.execFn()); |
165 | 28 | if (fn) this.end(fn); |
166 | 28 | return this; |
167 | }; | |
168 | ||
169 | /** | |
170 | * Force an execution timeout. | |
171 | * | |
172 | * @param {Number} ms | |
173 | * @returns {Runner} for chaining | |
174 | * @api public | |
175 | */ | |
176 | ||
177 | 1 | Runner.prototype.timeout = function(ms) { |
178 | 1 | this.command().timeout(ms); |
179 | 1 | this.expect(expect.time(ms)); |
180 | 1 | return this; |
181 | }; | |
182 | ||
183 | /** | |
184 | * Register a "stdout" expectation. | |
185 | * | |
186 | * @param {Regex|String} pattern | |
187 | * @returns {Runner} for chaining | |
188 | * @api public | |
189 | */ | |
190 | ||
191 | 1 | Runner.prototype.stdout = function(pattern) { |
192 | 12 | this.expect(expect.stdout(pattern)); |
193 | 12 | return this; |
194 | }; | |
195 | ||
196 | /** | |
197 | * Register a "stderr" expectation. | |
198 | * | |
199 | * @param {Regex|String} pattern | |
200 | * @returns {Runner} for chaining | |
201 | * @api public | |
202 | */ | |
203 | ||
204 | 1 | Runner.prototype.stderr = function(pattern) { |
205 | 6 | this.expect(expect.stderr(pattern)); |
206 | 6 | return this; |
207 | }; | |
208 | ||
209 | /** | |
210 | * Register an exit code expectation. | |
211 | * | |
212 | * @param {Number} code | |
213 | * @returns {Runner} for chaining | |
214 | * @api public | |
215 | */ | |
216 | ||
217 | 1 | Runner.prototype.code = function(code) { |
218 | 4 | this.expect(expect.code(code)); |
219 | 4 | return this; |
220 | }; | |
221 | ||
222 | /** | |
223 | * Check if a file or a directory exists. | |
224 | * | |
225 | * @param {String} path | |
226 | * @returns {Runner} for chaining | |
227 | * @api public | |
228 | */ | |
229 | ||
230 | 1 | Runner.prototype.exist = function(path) { |
231 | 4 | this.expect(expect.exists(path)); |
232 | 4 | return this; |
233 | }; | |
234 | ||
235 | /** | |
236 | * Match the content of a file. | |
237 | * | |
238 | * @param {Regex|String} pattern | |
239 | * @returns {Runner} for chaining | |
240 | * @api public | |
241 | */ | |
242 | ||
243 | 1 | Runner.prototype.match = function(file, pattern) { |
244 | 3 | this.expect(expect.match(file, pattern)); |
245 | 3 | return this; |
246 | }; | |
247 | ||
248 | /** | |
249 | * Create a new directory. | |
250 | * | |
251 | * @param {String} path | |
252 | * @returns {Runner} for chaining | |
253 | * @api public | |
254 | */ | |
255 | ||
256 | 1 | Runner.prototype.mkdir = function(path) { |
257 | 3 | this.batch.add(middlewares.mkdir(path)); |
258 | 3 | return this; |
259 | }; | |
260 | ||
261 | /** | |
262 | * Execute a command. | |
263 | * | |
264 | * @param {String} command | |
265 | * @returns {Runner} for chaining | |
266 | * @api public | |
267 | */ | |
268 | ||
269 | 1 | Runner.prototype.exec = function(cmd) { |
270 | 1 | this.batch.add(middlewares.exec(cmd)); |
271 | 1 | return this; |
272 | }; | |
273 | ||
274 | /** | |
275 | * Create a new file with the given `content`. | |
276 | * | |
277 | * @param {String} path | |
278 | * @param {String} data [optional] | |
279 | * @returns {Runner} for chaining | |
280 | * @api public | |
281 | */ | |
282 | ||
283 | 1 | Runner.prototype.writeFile = function(path, data) { |
284 | 6 | this.batch.add(middlewares.writeFile(path, data)); |
285 | 6 | return this; |
286 | }; | |
287 | ||
288 | /** | |
289 | * Remove a directory. | |
290 | * | |
291 | * @param {String} path | |
292 | * @returns {Runner} for chaining | |
293 | * @api public | |
294 | */ | |
295 | ||
296 | 1 | Runner.prototype.rmdir = function(path) { |
297 | 2 | this.batch.add(middlewares.rmdir(path)); |
298 | 2 | return this; |
299 | }; | |
300 | ||
301 | /** | |
302 | * Remove a file. | |
303 | * | |
304 | * @param {String} path | |
305 | * @returns {Runner} for chaining | |
306 | * @api public | |
307 | */ | |
308 | ||
309 | 1 | Runner.prototype.unlink = function(path) { |
310 | 6 | this.batch.add(middlewares.unlink(path)); |
311 | 6 | return this; |
312 | }; | |
313 | ||
314 | /** | |
315 | * Run the test. | |
316 | * | |
317 | * @param {Function} fn | |
318 | * @returns {Runner} for chaining | |
319 | * @api public | |
320 | */ | |
321 | ||
322 | 1 | Runner.prototype.end = function(fn) { |
323 | 30 | if (!this.batch.hasMain()) throw new Error('Please provide a command to run. Hint: `nixt#run`'); |
324 | 28 | this.batch.run(fn); |
325 | }; | |
326 | ||
327 | /** | |
328 | * Clone the runner. Give basic support for templates. | |
329 | * | |
330 | * @returns {Runner} clone of the current instance | |
331 | * @api public | |
332 | */ | |
333 | ||
334 | 1 | Runner.prototype.clone = function() { |
335 | 26 | return clone(this, false); |
336 | }; | |
337 | ||
338 | /** | |
339 | * Register an expectation. | |
340 | * | |
341 | * @param {Function} fn | |
342 | * @api public | |
343 | */ | |
344 | ||
345 | 1 | Runner.prototype.expect = function(fn) { |
346 | 30 | this.expectations.push(fn); |
347 | 30 | return this; |
348 | }; | |
349 | ||
350 | /** | |
351 | * Command factory. | |
352 | * | |
353 | * Return the current command (or create a new one | |
354 | * if none exists yet). | |
355 | * | |
356 | * @returns {Command} | |
357 | * @api private | |
358 | */ | |
359 | ||
360 | 1 | Runner.prototype.command = function() { |
361 | 62 | this._command = this._command || new Command(this.formatter); |
362 | 62 | return this._command; |
363 | }; | |
364 | ||
365 | /** | |
366 | * Return a function that will execute | |
367 | * the command. | |
368 | * | |
369 | * @returns {Function} | |
370 | * @api private | |
371 | */ | |
372 | ||
373 | 1 | Runner.prototype.execFn = function() { |
374 | 28 | var self = this; |
375 | ||
376 | 28 | return function(fn) { |
377 | 28 | self.command().exec(function(result) { |
378 | 28 | var err = null; |
379 | 28 | for (var i = 0, len = self.expectations.length; i < len; i++) { |
380 | 30 | err = self.expectations[i](result); |
381 | 39 | if (err) break; |
382 | } | |
383 | 28 | fn(err); |
384 | }); | |
385 | }; | |
386 | }; | |
387 | ||
388 | /** | |
389 | * Primary export. | |
390 | */ | |
391 | ||
392 | 1 | module.exports = Runner; |
393 |