1 | /// Module runner
|
2 | //
|
3 | // Runs a series of tests.
|
4 | //
|
5 | //
|
6 | // Copyright (c) 2013 Quildreen Motta
|
7 | //
|
8 | // Permission is hereby granted, free of charge, to any person
|
9 | // obtaining a copy of this software and associated documentation files
|
10 | // (the "Software"), to deal in the Software without restriction,
|
11 | // including without limitation the rights to use, copy, modify, merge,
|
12 | // publish, distribute, sublicense, and/or sell copies of the Software,
|
13 | // and to permit persons to whom the Software is furnished to do so,
|
14 | // subject to the following conditions:
|
15 | //
|
16 | // The above copyright notice and this permission notice shall be
|
17 | // included in all copies or substantial portions of the Software.
|
18 | //
|
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
20 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
21 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
22 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
23 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
24 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
25 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
26 | //
|
27 |
|
28 | //// -- Dependencies ---------------------------------------------------
|
29 | var Eventful = require('ekho').Eventful
|
30 | var pinky = require('pinky')
|
31 | var pipeline = require('pinky-combinators').pipeline
|
32 | var utils = require('./utils')
|
33 |
|
34 | var flatten = utils.flatten
|
35 | var sequentially = utils.sequentially
|
36 |
|
37 |
|
38 | //// -- Core implementation --------------------------------------------
|
39 |
|
40 | ///// {} Report
|
41 | //
|
42 | // Represents the result of running a collection of tests.
|
43 | //
|
44 | // :: Eventful <| Report
|
45 | var Report = Eventful.derive({
|
46 |
|
47 | ////// λ init
|
48 | //
|
49 | // Initialises a Report instance.
|
50 | //
|
51 | // :: @Report => Eventful -> ()
|
52 | init:
|
53 | function _init(parent) {
|
54 | Eventful.init.call(this, parent)
|
55 |
|
56 | this.passed = []
|
57 | this.failed = []
|
58 | this.ignored = []
|
59 | this.all = []
|
60 | this.started = new Date
|
61 |
|
62 | return this
|
63 | }
|
64 |
|
65 | ////// λ done
|
66 | //
|
67 | // Signals we've finished processing the Report.
|
68 | //
|
69 | // :: @Report => Report
|
70 | , done:
|
71 | function _done() {
|
72 | this.finished = new Date
|
73 | this.trigger('done', this)
|
74 |
|
75 | return this
|
76 | }
|
77 |
|
78 | ////// λ add
|
79 | //
|
80 | // Adds a test result to the Report.
|
81 | //
|
82 | // :: @Report => Result -> Report
|
83 | , add:
|
84 | function _add(result) {
|
85 | this.all.push(result)
|
86 | this.trigger('result', result)
|
87 |
|
88 | var verdict = result.verdict
|
89 | return verdict == 'success'? this.addSuccess(result)
|
90 | : verdict == 'failure'? this.addFailure(result)
|
91 | : verdict == 'ignored'? this.addIgnored(result)
|
92 | : /* otherwise */ this
|
93 | }
|
94 |
|
95 | ////// λ addFailure
|
96 | // :internal:
|
97 | // Adds a failure to the Report.
|
98 | //
|
99 | // :: @Report => Result -> Report
|
100 | , addFailure:
|
101 | function _addFailure(result) {
|
102 | this.failed.push(result)
|
103 | this.trigger('failure', result)
|
104 |
|
105 | return this
|
106 | }
|
107 |
|
108 | ////// λ addSuccess
|
109 | // :internal:
|
110 | // Adds a success to the Report.
|
111 | //
|
112 | // :: @Report => Result -> Report
|
113 | , addSuccess:
|
114 | function _addSuccess(result) {
|
115 | this.passed.push(result)
|
116 | this.trigger('success', result)
|
117 |
|
118 | return this
|
119 | }
|
120 |
|
121 | ////// λ addIgnored
|
122 | // :internal:
|
123 | // Adds a ignored result to the Report.
|
124 | //
|
125 | // :: @Report => Result -> Report
|
126 | , addIgnored:
|
127 | function _addIgnored(result) {
|
128 | this.ignored.push(result)
|
129 | this.trigger('ignored', result)
|
130 |
|
131 | return this
|
132 | }
|
133 | })
|
134 |
|
135 |
|
136 | ///// λ run
|
137 | //
|
138 | // Runs a series of test cases.
|
139 | //
|
140 | // Optionally we take a reporter, which will be used to bind listeners
|
141 | // to the eventful reporter. This is used for interfaces that need to
|
142 | // update frequently to provide feedback for the user.
|
143 | //
|
144 | // The `report` argument is used for passing around the top-level
|
145 | // eventful object when recursing through the nested suites.
|
146 | //
|
147 | // :: [Runnable], (Reporter -> ()), Report? -> Promise [Result]
|
148 | function run(tests, reporter, report) {
|
149 | var reportDone
|
150 | if (!report) {
|
151 | reportDone = true
|
152 | report = Report.make(null)
|
153 | report.on('test:finished', function(ev, result) {
|
154 | report.add(result)
|
155 | })
|
156 | }
|
157 | if (reporter) reporter(report)
|
158 |
|
159 | var promise = sequentially(tests.map(runnerForThing))
|
160 | if (reportDone) promise = promise.then(function(results){
|
161 | report.done()
|
162 | return report.all
|
163 | })
|
164 |
|
165 | return promise
|
166 |
|
167 | function runnerForThing(test) {
|
168 | return test.tests? runnerForSuite(test)
|
169 | : /* otherwise */ runnerForTest(test)
|
170 | }
|
171 |
|
172 | function runnerForSuite(suite) { return function() {
|
173 | report.trigger('suite:started', suite)
|
174 | return suite.run(report)
|
175 | .then(function(xs) {
|
176 | var results = flatten(xs)
|
177 | report.trigger('suite:finished', results, suite)
|
178 | return results
|
179 | })
|
180 | }}
|
181 |
|
182 | function runnerForTest(test) { return function() {
|
183 | report.trigger('test:started', test)
|
184 | return test.run(reporter)
|
185 | .then(function(result) {
|
186 | report.trigger('test:finished', result, test)
|
187 | return result
|
188 | })
|
189 | }}
|
190 | }
|
191 |
|
192 | //// -- Exports --------------------------------------------------------
|
193 | module.exports = { run: run
|
194 | , Report: Report } |
\ | No newline at end of file |