1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 | fs = require 'fs'
|
12 | path = require 'path'
|
13 | http = require 'http'
|
14 | websocket = require './websocket'
|
15 | Mocha = require 'mocha'
|
16 |
|
17 | debug = require('debug')('fbp-spec:mochacompat')
|
18 | testsuite = require './testsuite'
|
19 |
|
20 | loadTests = (files) ->
|
21 | options = {}
|
22 | mocha = new Mocha options
|
23 |
|
24 | for f in files
|
25 | resolved = require.resolve f
|
26 | delete require.cache[resolved]
|
27 | mocha.addFile f
|
28 | mocha.loadFiles()
|
29 | return mocha
|
30 |
|
31 |
|
32 | runTests = (mocha, progress, callback) ->
|
33 |
|
34 | suite = mocha.suite
|
35 | options = mocha.options
|
36 | options.files = mocha.files
|
37 | runner = new Mocha.Runner suite, options.delay
|
38 | registerReporter = (r) ->
|
39 | runner.on 'pass', (test) ->
|
40 | progress null, test
|
41 | runner.on 'fail', (test, err) ->
|
42 | progress err, test
|
43 |
|
44 | mocha.reporter registerReporter, {}
|
45 | reporter = new mocha._reporter runner, options
|
46 |
|
47 | runner.ignoreLeaks = options.ignoreLeaks != false
|
48 | runner.fullStackTrace = options.fullStackTrace
|
49 | runner.asyncOnly = options.asyncOnly
|
50 | runner.allowUncaught = options.allowUncaught
|
51 | if options.grep
|
52 | runner.grep options.grep, options.invert
|
53 | if options.globals
|
54 | runner.globals options.globals
|
55 | if options.growl
|
56 | mocha._growl runner, reporter
|
57 | if options.useColors?
|
58 | Mocha.reporters.Base.useColors = options.useColors
|
59 | Mocha.reporters.Base.inlineDiffs = options.useInlineDiffs
|
60 |
|
61 | done = (failures) ->
|
62 | if reporter.done
|
63 | reporter.done failures, callback
|
64 | else
|
65 | callback && callback failures
|
66 |
|
67 | return runner.run done
|
68 |
|
69 | testId = (fullname) ->
|
70 | crypto = require 'crypto'
|
71 | hash = crypto.createHash 'sha256'
|
72 | hash.update fullname
|
73 | return hash.digest('hex').substr(0, 10)
|
74 |
|
75 | loadSuite = (fbpSuite, suite) ->
|
76 |
|
77 | for testcase in suite.tests
|
78 |
|
79 |
|
80 | fullName = fbpSuite.name + testcase.parent.title + testcase.title
|
81 | id = testId fullName
|
82 | testcase._fbpid = id
|
83 | fbpCase =
|
84 | name: testcase.parent.title
|
85 | assertion: testcase.title
|
86 | _id: id
|
87 | inputs:
|
88 | test: id
|
89 | expect:
|
90 | error:
|
91 | noterror: null
|
92 |
|
93 | fbpSuite.cases.push fbpCase
|
94 |
|
95 |
|
96 | for sub in suite.suites
|
97 | loadSuite fbpSuite, sub
|
98 |
|
99 | buildFbpSpecs = (mocha) ->
|
100 | specs = []
|
101 |
|
102 | top = mocha.suite
|
103 | for suite in top.suites
|
104 |
|
105 |
|
106 | fbpSuite = testsuite.create
|
107 | name: "#{suite.title} (Mocha tests)"
|
108 | fixture:
|
109 | type: 'fbp'
|
110 | data: """
|
111 | # @runtime fbp-spec-mocha
|
112 |
|
113 | INPORT=runTest.IN:TEST
|
114 | OUTPORT=runTest.ERROR:ERROR
|
115 |
|
116 | runTest(mocha/LoadTestCase) OUT -> IN verifyResult(mocha/CheckResults)
|
117 | """
|
118 |
|
119 | loadSuite fbpSuite, suite
|
120 | specs.push fbpSuite
|
121 |
|
122 | return specs
|
123 |
|
124 | dumpSpecs = (suites) ->
|
125 | jsyaml = window.jsyaml if window?.jsyaml?
|
126 | jsyaml = require 'js-yaml' if not jsyaml
|
127 |
|
128 | str = ""
|
129 | delimiter = '---\n'
|
130 | for s in suites
|
131 | str += "#{jsyaml.safeDump s}"
|
132 | str += delimiter if suites.length > 1
|
133 |
|
134 | return str
|
135 |
|
136 | discoverHost = (preferred_iface) ->
|
137 | os = require 'os'
|
138 |
|
139 | ifaces = os.networkInterfaces()
|
140 | address = undefined
|
141 | int_address = undefined
|
142 |
|
143 | filter = (connection) ->
|
144 | if connection.family != 'IPv4'
|
145 | return
|
146 | if connection.internal
|
147 | int_address = connection.address
|
148 | else
|
149 | address = connection.address
|
150 | return
|
151 |
|
152 | if typeof preferred_iface == 'string' and preferred_iface in ifaces
|
153 | ifaces[preferred_iface].forEach filter
|
154 | else
|
155 | for device of ifaces
|
156 | ifaces[device].forEach filter
|
157 | address or int_address
|
158 |
|
159 | knownUnsupportedCommands = (p, c) ->
|
160 | return false
|
161 |
|
162 | fbpComponentName = (s) ->
|
163 | return "fbp-spec-mocha/#{s.name}"
|
164 |
|
165 | fbpComponentFromSpec = (s) ->
|
166 |
|
167 | p =
|
168 | name: fbpComponentName(s)
|
169 | subgraph: false
|
170 | inPorts: []
|
171 | outPorts: []
|
172 |
|
173 | fbpSourceFromSpec = (s) ->
|
174 |
|
175 | serialized = dumpSpecs [s]
|
176 | p =
|
177 | name: fbpComponentName(s)
|
178 | code: ''
|
179 | language: 'whitespace'
|
180 | tests: serialized
|
181 |
|
182 | handleFbpCommand = (state, runtime, mocha, specs, protocol, command, payload, context) ->
|
183 |
|
184 | updateStatus = (news, event) ->
|
185 | state.started = news.started if news.started?
|
186 | state.running = news.running if news.running?
|
187 | runtimeState = { started: state.started, running: state.running, graph: payload.graph }
|
188 | debug 'update status', runtimeState
|
189 | runtime.send 'network', event, runtimeState, context
|
190 |
|
191 |
|
192 |
|
193 | ackMessage = ->
|
194 |
|
195 | runtime.send protocol, command, payload, context
|
196 |
|
197 |
|
198 | if protocol == 'runtime' and command == 'getruntime'
|
199 | capabilities = [
|
200 | 'protocol:graph'
|
201 | 'protocol:component'
|
202 | 'protocol:network'
|
203 | 'protocol:runtime'
|
204 | 'component:getsource'
|
205 | ]
|
206 | info =
|
207 | type: 'fbp-spec-mocha'
|
208 | version: '0.5'
|
209 | capabilities: capabilities
|
210 | allCapabilities: capabilities
|
211 | graph: 'default/main'
|
212 | runtime.send 'runtime', 'runtime', info, context
|
213 |
|
214 |
|
215 |
|
216 | else if protocol == 'runtime' and command == 'packet'
|
217 | debug 'test message', payload, state.running
|
218 |
|
219 | if payload.port != 'test' or payload.event != 'data'
|
220 | debug 'unexpected test message format'
|
221 | return
|
222 |
|
223 | state.currentTest = payload.payload
|
224 |
|
225 |
|
226 | testDone = (err, test) ->
|
227 | debug 'test completed', test._fbpid, err, Object.keys(state.completedTests).length
|
228 | state.completedTests[test._fbpid] = { test: test, err: err }
|
229 | checkSendCurrentTest()
|
230 |
|
231 | checkSendCurrentTest = () ->
|
232 | completed = state.completedTests[state.currentTest]
|
233 | debug 'checking for', state.currentTest, completed?
|
234 | if completed
|
235 | m =
|
236 | graph: state.graph
|
237 | event: 'data'
|
238 | port: 'error'
|
239 | payload: completed.err
|
240 | runtime.send 'runtime', 'packet', m, context
|
241 | delete state.completedTests[state.currentTest]
|
242 |
|
243 | checkSendCurrentTest()
|
244 |
|
245 | if not state.running
|
246 | runTests mocha, testDone, (f) ->
|
247 | updateStatus { running: false }, 'status'
|
248 |
|
249 | updateStatus { running: true }, 'status'
|
250 |
|
251 |
|
252 | else if protocol == 'graph' and command == 'addnode'
|
253 | ackMessage()
|
254 | else if protocol == 'graph' and command == 'addedge'
|
255 | ackMessage()
|
256 | else if protocol == 'graph' and command == 'addinport'
|
257 | ackMessage()
|
258 | else if protocol == 'graph' and command == 'addoutport'
|
259 | ackMessage()
|
260 | else if protocol == 'graph' and command == 'clear'
|
261 | state.graph = payload.id
|
262 | debug 'new graph', state.graph
|
263 | ackMessage()
|
264 |
|
265 |
|
266 | else if protocol == 'network' and command == 'getstatus'
|
267 | runtime.send 'network', 'status', state, context
|
268 |
|
269 | else if protocol == 'network' and command == 'start'
|
270 | debug 'FBP network start'
|
271 | updateStatus { started: true, running: false, graph: payload.graph }, 'started'
|
272 | else if protocol == 'network' and command == 'stop'
|
273 | debug 'FBP network stop'
|
274 | updateStatus { started: false, running: false, graph: payload.graph }, 'stopped'
|
275 |
|
276 |
|
277 | else if protocol == 'component' and command == 'list'
|
278 |
|
279 | for s in specs
|
280 | runtime.send 'component', 'component', fbpComponentFromSpec(s), context
|
281 | runtime.send 'component', 'componentsready', specs.length, context
|
282 |
|
283 | else if protocol == 'component' and command == 'getsource'
|
284 |
|
285 | found = null
|
286 | for s in specs
|
287 | componentName = fbpComponentName s
|
288 | if componentName == payload.name
|
289 | found = s
|
290 | debug 'component getsource', "'#{payload.name}'", found?.name
|
291 | if found
|
292 | runtime.send 'component', 'source', fbpSourceFromSpec(found), context
|
293 |
|
294 | else if knownUnsupportedCommands protocol, command
|
295 |
|
296 | else
|
297 | debug 'Warning: Unknown FBP protocol message', protocol, command
|
298 |
|
299 |
|
300 | normalizeOptions = (options) ->
|
301 | if options.host == 'autodetect'
|
302 | options.host = discoverHost()
|
303 | else if match = /autodetect\(([a-z0-9]+)\)/.exec(options.host)
|
304 | options.host = discoverHost(match[1])
|
305 |
|
306 | options.port = 3333 if not options.port
|
307 |
|
308 | return options
|
309 |
|
310 | parse = (args) ->
|
311 | program = require 'commander'
|
312 |
|
313 |
|
314 | program
|
315 | .arguments('<files...>')
|
316 | .action (args) ->
|
317 | program.files = args
|
318 | .option('--host <hostname>', 'Hostname we serve on, for live-url', String, 'autodetect')
|
319 | .option('--port <PORT>', 'Command to launch runtime under test', Number, 3333)
|
320 | .parse(process.argv)
|
321 |
|
322 | return program
|
323 |
|
324 | exports.setup = setup = (options, callback) ->
|
325 | options = normalizeOptions options
|
326 |
|
327 | mocha = loadTests options.files
|
328 | specs = buildFbpSpecs mocha
|
329 |
|
330 | state =
|
331 | started: false
|
332 | running: false
|
333 | currentTest: null
|
334 | graph: null
|
335 | specs: specs
|
336 | completedTests: {}
|
337 | httpServer = new http.Server
|
338 | runtime = websocket httpServer, {}
|
339 | runtime.receive = (protocol, command, payload, context) ->
|
340 | handleFbpCommand state, runtime, mocha, specs, protocol, command, payload, context
|
341 |
|
342 | httpServer.listen options.port, (err) ->
|
343 | return callback err, state, httpServer
|
344 |
|
345 | exports.main = main = () ->
|
346 |
|
347 | options = parse process.argv
|
348 |
|
349 | setup options, (err, state) ->
|
350 | throw err if err
|
351 | console.log "fbp-spec-mocha started on ws://#{options.host}:#{options.port}"
|
352 | console.log "found #{state.specs.length} test suites"
|
353 |
|
354 | main() if not module.parent
|
355 |
|