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 |
|
33 |
|
34 | //// -- Core implementation --------------------------------------------
|
35 |
|
36 | ///// {} Report
|
37 | //
|
38 | // Represents the result of running a collection of tests.
|
39 | //
|
40 | // :: Eventful <| Report
|
41 | var Report = Eventful.derive({
|
42 |
|
43 | ////// λ init
|
44 | //
|
45 | // Initialises a Report instance.
|
46 | //
|
47 | // :: @Report => Eventful -> ()
|
48 | init:
|
49 | function _init(parent) {
|
50 | Eventful.init.call(this, parent)
|
51 |
|
52 | this.passed = []
|
53 | this.failed = []
|
54 | this.ignored = []
|
55 | this.all = []
|
56 | this.started = new Date
|
57 |
|
58 | return this
|
59 | }
|
60 |
|
61 | ////// λ done
|
62 | //
|
63 | // Signals we've finished processing the Report.
|
64 | //
|
65 | // :: @Report => Report
|
66 | , done:
|
67 | function _done() {
|
68 | this.finished = new Date
|
69 | this.trigger('done', this)
|
70 |
|
71 | return this
|
72 | }
|
73 |
|
74 | ////// λ add
|
75 | //
|
76 | // Adds a test result to the Report.
|
77 | //
|
78 | // :: @Report => Result -> Report
|
79 | , add:
|
80 | function _add(result) {
|
81 | this.all.push(result)
|
82 | this.trigger('result', result)
|
83 |
|
84 | var verdict = result.verdict
|
85 | return verdict == 'success'? this.addSuccess(result)
|
86 | : verdict == 'failure'? this.addFailure(result)
|
87 | : verdict == 'ignored'? this.addIgnored(result)
|
88 | : /* otherwise */ this
|
89 | }
|
90 |
|
91 | ////// λ addFailure
|
92 | // :internal:
|
93 | // Adds a failure to the Report.
|
94 | //
|
95 | // :: @Report => Result -> Report
|
96 | , addFailure:
|
97 | function _addFailure(result) {
|
98 | this.failed.push(result)
|
99 | this.trigger('failure', result)
|
100 |
|
101 | return this
|
102 | }
|
103 |
|
104 | ////// λ addSuccess
|
105 | // :internal:
|
106 | // Adds a success to the Report.
|
107 | //
|
108 | // :: @Report => Result -> Report
|
109 | , addSuccess:
|
110 | function _addSuccess(result) {
|
111 | this.passed.push(result)
|
112 | this.trigger('success', result)
|
113 |
|
114 | return this
|
115 | }
|
116 |
|
117 | ////// λ addIgnored
|
118 | // :internal:
|
119 | // Adds a ignored result to the Report.
|
120 | //
|
121 | // :: @Report => Result -> Report
|
122 | , addIgnored:
|
123 | function _addIgnored(result) {
|
124 | this.ignored.push(result)
|
125 | this.trigger('ignored', result)
|
126 |
|
127 | return this
|
128 | }
|
129 | })
|
130 |
|
131 | ///// λ sequentially
|
132 | //
|
133 | // Runs a series of promise-returning functions sequentially.
|
134 | //
|
135 | // :: [a... -> Promise b] -> Promise [b]
|
136 | function sequentially(fns) {
|
137 | if (fns.length == 0) return pinky([])
|
138 |
|
139 | var result = []
|
140 | var promise = pinky()
|
141 |
|
142 | next()
|
143 |
|
144 | return promise
|
145 |
|
146 | function next() {
|
147 | var f = fns.shift()
|
148 | f().always(function(value) {
|
149 | result.push(value)
|
150 | if (!fns.length) promise.fulfill()
|
151 | else next()
|
152 | })
|
153 | }
|
154 | }
|
155 |
|
156 |
|
157 | ///// λ run
|
158 | //
|
159 | // Runs a series of test cases.
|
160 | //
|
161 | // Optionally we take a reporter, which will be used to bind listeners
|
162 | // to the eventful reporter. This is used for interfaces that need to
|
163 | // update frequently to provide feedback for the user.
|
164 | //
|
165 | // The `report` argument is used for passing around the top-level
|
166 | // eventful object when recursing through the nested suites.
|
167 | //
|
168 | // :: [Runnable], (Reporter -> ()), Report? -> Promise [Result]
|
169 | function run(tests, reporter, report) {
|
170 | var reportDone
|
171 | if (!report) {
|
172 | reportDone = true
|
173 | report = Report.make(null)
|
174 | report.on('test:finished', function(ev, result) {
|
175 | report.add(result)
|
176 | })
|
177 | }
|
178 | if (reporter) reporter(report)
|
179 |
|
180 | var promise = sequentially(tests.map(runnerForThing))
|
181 | if (reportDone) promise = promise.then(function(results){
|
182 | report.done()
|
183 | return report.all
|
184 | })
|
185 |
|
186 | return promise
|
187 |
|
188 | function runnerForThing(test) {
|
189 | return test.tests? runnerForSuite(test)
|
190 | : /* otherwise */ runnerForTest(test)
|
191 | }
|
192 |
|
193 | function runnerForSuite(suite) { return function() {
|
194 | report.trigger('suite:started', suite)
|
195 | return suite.run(report)
|
196 | .then(function(result) {
|
197 | report.trigger('suite:finished', result, suite)
|
198 | return result
|
199 | })
|
200 | }}
|
201 |
|
202 | function runnerForTest(test) { return function() {
|
203 | report.trigger('test:started', test)
|
204 | return test.run(reporter)
|
205 | .then(function(result) {
|
206 | report.trigger('test:finished', result, test)
|
207 | return result
|
208 | })
|
209 | }}
|
210 | }
|
211 |
|
212 | //// -- Exports --------------------------------------------------------
|
213 | module.exports = { run: run
|
214 | , Report: Report } |
\ | No newline at end of file |