UNPKG

5.75 kBJavaScriptView Raw
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 ---------------------------------------------------
29var Eventful = require('ekho').Eventful
30var pinky = require('pinky')
31var 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
41var 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]
136function 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]
169function 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 --------------------------------------------------------
213module.exports = { run: run
214 , Report: Report }
\No newline at end of file