bellite 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +29 -0
- data/Rakefile +2 -0
- data/bellite.gemspec +17 -0
- data/lib/bellite/version.rb +3 -0
- data/lib/bellite.rb +729 -0
- data/test/_doBelliteTest.rb +29 -0
- data/test/testClientJSONRPC.js +264 -0
- data/test/testClientJSONRPC.py +21 -0
- metadata +79 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Ivan
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Bellite
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'bellite'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install bellite
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/bellite.gemspec
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/bellite/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Ivan"]
|
6
|
+
gem.email = ["asmer@asmer.org.ua"]
|
7
|
+
gem.description = "Bellite JSON-RPC Client library"
|
8
|
+
gem.summary = "Implements connection to JSON-RPC server, calling remote methods and bindings to server events"
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "bellite"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Bellite::VERSION
|
17
|
+
end
|
data/lib/bellite.rb
ADDED
@@ -0,0 +1,729 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
require 'json'
|
3
|
+
require 'socket'
|
4
|
+
include Socket::Constants
|
5
|
+
|
6
|
+
|
7
|
+
# Clone of {http://docs.python.org/2/library/functools.html#functools.partial Python functools.partial}
|
8
|
+
# Creates a lambda with passed func and applies to it args. Args can be overriden when this lambda called.
|
9
|
+
#
|
10
|
+
#@param [Proc] func - func to be called with partial parameters
|
11
|
+
#@param [Array] *args - default parameters which should be passed to func
|
12
|
+
#@return [Proc] wrapper around func which passes *args or new args, passed to wrapper to func
|
13
|
+
def partial(func, *args)
|
14
|
+
return Proc.new do |*new_args|
|
15
|
+
merged_params = args.clone()
|
16
|
+
new_args.each_with_index {|v,k|
|
17
|
+
merged_params[k] = new_args[k]
|
18
|
+
}
|
19
|
+
func.call(*merged_params)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
# Simple implementation of {http://docs.python.org/2/library/asyncore.html Python Asyncore}
|
25
|
+
class Async
|
26
|
+
#!attribute @@map
|
27
|
+
# List of objects, implements asyncore methods
|
28
|
+
@@map = []
|
29
|
+
|
30
|
+
#Checks sockets from map objects using {IO.select}
|
31
|
+
#@param [Float] timeout timeout in seconds, passed to {IO.select}
|
32
|
+
#@param [Hash] map list of objects implements asyncore methods
|
33
|
+
def Async.check(timeout=false, map=false)
|
34
|
+
if not map
|
35
|
+
map = @@map
|
36
|
+
end
|
37
|
+
readable = []
|
38
|
+
writable = []
|
39
|
+
excepted = []
|
40
|
+
|
41
|
+
changedCount = 0
|
42
|
+
map.each do |obj|
|
43
|
+
if obj.writable?
|
44
|
+
if obj.fileno
|
45
|
+
writable << obj.fileno
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
if obj.readable?
|
50
|
+
if obj.fileno
|
51
|
+
readable << obj.fileno
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
if obj.exceptable?
|
56
|
+
if obj.fileno
|
57
|
+
excepted << obj.fileno
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
if readable.size == 0 and writable.size == 0 and excepted.size == 0
|
63
|
+
return false
|
64
|
+
end
|
65
|
+
|
66
|
+
if (timeout)
|
67
|
+
r,w,e = IO.select(readable, writable, excepted,timeout)
|
68
|
+
else
|
69
|
+
r,w,e = IO.select(readable, writable, excepted)
|
70
|
+
end
|
71
|
+
map.each do |obj|
|
72
|
+
if obj.writable? and w.include? obj.fileno
|
73
|
+
obj.handle_write_event
|
74
|
+
end
|
75
|
+
if obj.readable? and r.include? obj.fileno
|
76
|
+
obj.handle_read_event
|
77
|
+
end
|
78
|
+
if obj.exceptable? and e.include? obj.fileno
|
79
|
+
obj.handle_expt_event
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
return r.size + w.size + e.size
|
84
|
+
end
|
85
|
+
|
86
|
+
#Running check while at least one socket in objects map is connected
|
87
|
+
#@param [Float] timeout - timeout, passed to {.check}
|
88
|
+
#@param [Hash] map, passed to {.check}
|
89
|
+
def Async.loop(timeout,map=false)
|
90
|
+
while Async.check(timeout, map) != false
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
|
97
|
+
# Implements new pythonic {http://docs.python.org/2/library/stdtypes.html#dict.setdefault setdefault} method in ruby class Hash
|
98
|
+
class Hash
|
99
|
+
#(see {Hash})
|
100
|
+
#@param key key of hash
|
101
|
+
#@param value value of key in hash
|
102
|
+
#@return [Value] if key in hash - hash value, attached to key; otherwise value, passed to setdefault
|
103
|
+
def setdefault(key, value)
|
104
|
+
if self[key] == nil
|
105
|
+
self[key] = value
|
106
|
+
end
|
107
|
+
return self[key]
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
# @abstract This is common interface of Bellite json-rpc API
|
113
|
+
class BelliteJsonRpcApi
|
114
|
+
#Constructor. Connects to server with cred credentials
|
115
|
+
#@param [String] cred Credentials in format: 'host:port/token';
|
116
|
+
def initialize(cred)
|
117
|
+
cred = findCredentials(cred)
|
118
|
+
if cred
|
119
|
+
_connect(cred)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
#Authenticates with server using token
|
124
|
+
#@param [String] token Token to access server
|
125
|
+
#@return {Promise}
|
126
|
+
def auth(token)
|
127
|
+
return _invoke('auth', [token])
|
128
|
+
end
|
129
|
+
|
130
|
+
#Server version
|
131
|
+
#@return {Promise}
|
132
|
+
def version
|
133
|
+
return _invoke('version')
|
134
|
+
end
|
135
|
+
|
136
|
+
#Ping server
|
137
|
+
#@return {Promise}
|
138
|
+
def ping
|
139
|
+
return _invoke('ping')
|
140
|
+
end
|
141
|
+
|
142
|
+
#invokes respondsTo
|
143
|
+
#@param [Fixnum] selfId Id for internal use (events, for example)
|
144
|
+
#@param [Hash] cmd - some data, which can be encoded as JSON to pass to server
|
145
|
+
#@return {Promise}
|
146
|
+
def respondsTo(selfId, cmd)
|
147
|
+
if not selfId
|
148
|
+
selfId = 0
|
149
|
+
end
|
150
|
+
return _invoke('respondsTo', [selfId, cmd])
|
151
|
+
end
|
152
|
+
|
153
|
+
#perform JSON-RPC request
|
154
|
+
#@param [String] cmd command
|
155
|
+
#@param [Array] *args Variable-length arguments, passed with cmd. :key => value ruby Hash parameter sugar also works
|
156
|
+
#@return [Promise]
|
157
|
+
def perform(selfId, cmd, *args)
|
158
|
+
if args.size > 1
|
159
|
+
args.each do |arg|
|
160
|
+
if arg.instance_of(Hash)
|
161
|
+
raise ArgumentError, "Cannot specify both positional and keyword arguments"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
if args.size == 1 and (args[0].instance_of? Hash or args[0].instance_of? Array)
|
166
|
+
args = args[0]
|
167
|
+
end
|
168
|
+
if args == []
|
169
|
+
args = nil
|
170
|
+
end
|
171
|
+
if not selfId
|
172
|
+
selfId = 0
|
173
|
+
end
|
174
|
+
return _invoke('perform',[selfId, cmd, args])
|
175
|
+
end
|
176
|
+
|
177
|
+
# binds Event on some evtType
|
178
|
+
#@param [Fixnum] selfId internal id
|
179
|
+
#@param [String] evtType type of event
|
180
|
+
#@param [Fixnum] res
|
181
|
+
#@param [Hash] ctx - context, passed to event handler
|
182
|
+
#@return [Promise]
|
183
|
+
def bindEvent(selfId=0, evtType='*', res = -1, ctx=false)
|
184
|
+
if not selfId
|
185
|
+
selfId = 0
|
186
|
+
end
|
187
|
+
return _invoke('bindEvent',[selfId, evtType, res, ctx])
|
188
|
+
end
|
189
|
+
|
190
|
+
# Unbins Event on some evtType
|
191
|
+
#@param [Fixnum] selfId internal id
|
192
|
+
#@param [String] evtType type of event
|
193
|
+
#@return [Promise]
|
194
|
+
def unbindEvent(selfId, evtType=false)
|
195
|
+
if not selfId
|
196
|
+
selfId = 0
|
197
|
+
end
|
198
|
+
return _invoke('unbindEvent',[selfId, evtType])
|
199
|
+
end
|
200
|
+
|
201
|
+
|
202
|
+
# Finds credentials in environment variable BELLITE_SERVER or in passed parameter
|
203
|
+
#@param [String] cred server credentials in format host:port/token
|
204
|
+
#@return [Hash] with credentials or false if failed
|
205
|
+
def findCredentials(cred=false)
|
206
|
+
if not cred
|
207
|
+
cred = ENV['BELLITE_SERVER']
|
208
|
+
if not cred
|
209
|
+
cred = '127.0.0.1:3099/bellite-demo-host';
|
210
|
+
$stderr.puts 'BELLITE_SERVER environment variable not found, using "'+cred+'"'
|
211
|
+
end
|
212
|
+
elsif not cred.instance_of String
|
213
|
+
return cred
|
214
|
+
end
|
215
|
+
|
216
|
+
begin
|
217
|
+
host, token = cred.split('/', 2)
|
218
|
+
host, port = host.split(':', 2)
|
219
|
+
return ({"credentials" => cred, "token" => token, "host" => host, "port" => Integer(port)})
|
220
|
+
rescue
|
221
|
+
return false
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
#@abstract Connecting to JSON-RPC server
|
226
|
+
#@param [String] host Host
|
227
|
+
#@param [Fixnum] port Port
|
228
|
+
def _connect(host, port)
|
229
|
+
raise NotImplementedError, "Subclass Responsibility"
|
230
|
+
end
|
231
|
+
|
232
|
+
#@abstract Invokes method
|
233
|
+
#@param [String] method
|
234
|
+
#@param [Hash] params Something JSONable to pass as ethod params
|
235
|
+
def _invoke(method, params=nil)
|
236
|
+
raise NotImplementedError, "Subclass Responsibility"
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
|
241
|
+
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
242
|
+
#
|
243
|
+
|
244
|
+
|
245
|
+
#@abstract Next level of Bellite server API implementation: Basic operations with server like Bellite.
|
246
|
+
class BelliteJsonRpc < BelliteJsonRpcApi
|
247
|
+
#constructor.
|
248
|
+
#@param [String] cred Server credentials
|
249
|
+
#@param [Boolean] logging Logging flag
|
250
|
+
def initialize(cred=false, logging=false)
|
251
|
+
@_resultMap = {}
|
252
|
+
@_evtTypeMap = {}
|
253
|
+
@_nextMsgId = 100
|
254
|
+
super (cred)
|
255
|
+
@logging = logging
|
256
|
+
end
|
257
|
+
|
258
|
+
#Notify JSON-RPC server by method call, but skip response
|
259
|
+
#@param [String] method Method to call
|
260
|
+
#@param [Hash,Array,String,Float,Fixnum] params parameters for method
|
261
|
+
#@return [Boolean] true if sent
|
262
|
+
def _notify(method, params=nil)
|
263
|
+
return _sendJsonRpc(method, params)
|
264
|
+
end
|
265
|
+
|
266
|
+
#Calls JSON-RPC method
|
267
|
+
#@param [String] method Method to call
|
268
|
+
#@param [Hash,Array,String,Float,Fixnum] params parameters for method
|
269
|
+
#@return [Promise]
|
270
|
+
def _invoke(method, params=nil)
|
271
|
+
msgId = @_nextMsgId
|
272
|
+
@_nextMsgId += 1
|
273
|
+
res = _newResult(msgId)
|
274
|
+
_sendJsonRpc(method, params, msgId)
|
275
|
+
return res.promise
|
276
|
+
end
|
277
|
+
|
278
|
+
#returns new Promise object, attached to msgId
|
279
|
+
#@param [Fixnum] msgId id of message for this result
|
280
|
+
#@return [Promise]
|
281
|
+
def _newResult(msgId)
|
282
|
+
res = deferred()
|
283
|
+
@_resultMap[msgId] = res
|
284
|
+
return res
|
285
|
+
end
|
286
|
+
|
287
|
+
#Sends JSON-RPC call to server
|
288
|
+
#@param [String] method Method to call
|
289
|
+
#@param [Hash,Array,String,Float,Fixnum] params parameters for method
|
290
|
+
#@param [Fixnum] msgId Message Id. If default, it uses internal autoincrement counter
|
291
|
+
#@return [Boolean] True if sent
|
292
|
+
def _sendJsonRpc(method, params=nil, msgId=false)
|
293
|
+
msg = {"jsonrpc" => "2.0", "method" => method}
|
294
|
+
if params
|
295
|
+
msg['params'] = params
|
296
|
+
end
|
297
|
+
if msgId
|
298
|
+
msg['id'] = msgId
|
299
|
+
end
|
300
|
+
logSend(msg)
|
301
|
+
return _sendMessage(JSON.fast_generate(msg))
|
302
|
+
end
|
303
|
+
|
304
|
+
#Sends JSON-RPC string to server
|
305
|
+
#@param [String] msg JSON-encoded JSON-RPC call to send
|
306
|
+
#@return [Boolean] True if sent
|
307
|
+
def _sendMessage(msg)
|
308
|
+
raise NotImplementedError('Subclass Responsibility')
|
309
|
+
end
|
310
|
+
|
311
|
+
#Puts send packets to STDOUT
|
312
|
+
#@param [String] msg Same as for _sendMessage
|
313
|
+
def logSend(msg)
|
314
|
+
puts "send ==> " + JSON.fast_generate(msg)
|
315
|
+
end
|
316
|
+
|
317
|
+
#Puts received packets to STDOUT
|
318
|
+
#@param [String] msg Same as for _sendMessage
|
319
|
+
def logRecv(msg)
|
320
|
+
puts "recv ==> " + JSON.fast_generate(msg)
|
321
|
+
end
|
322
|
+
|
323
|
+
#Receives JSON-RPC response or call from JSON-RPC Server
|
324
|
+
#@param [Array<String>] msgList Array of messages from Server
|
325
|
+
def _recvJsonRpc(msgList)
|
326
|
+
msgList.each do |msg|
|
327
|
+
begin
|
328
|
+
msg = JSON.parse(msg)
|
329
|
+
isCall = msg.has_key?("method")
|
330
|
+
rescue
|
331
|
+
next
|
332
|
+
end
|
333
|
+
logRecv(msg)
|
334
|
+
begin
|
335
|
+
if isCall
|
336
|
+
on_rpc_call(msg)
|
337
|
+
else
|
338
|
+
on_rpc_response(msg)
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
#Called on JSON-RPC call FROM server.
|
345
|
+
#@param [String] msg Server response with call
|
346
|
+
def on_rpc_call(msg)
|
347
|
+
if msg['method'] == 'event'
|
348
|
+
args = msg['params']
|
349
|
+
emit(args['evtType'], args)
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
#Called on JSON-RPC response (not call) from Server
|
354
|
+
#@param [String] msg Server respon with data
|
355
|
+
def on_rpc_response(msg)
|
356
|
+
tgt = @_resultMap.delete msg['id']
|
357
|
+
if tgt == nil
|
358
|
+
return
|
359
|
+
end
|
360
|
+
|
361
|
+
if msg.has_key?('error')
|
362
|
+
tgt.reject.call(msg['error'])
|
363
|
+
else
|
364
|
+
tgt.resolve.call(msg['result'])
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
#Called on connect to remote JSON-RPC server.
|
369
|
+
#@param [Hash] cred Server credentials: port, host, token and original `credentials` string
|
370
|
+
def on_connect(cred)
|
371
|
+
auth(cred['token'])._then.call(method(:on_auth_succeeded), method(:on_auth_failed))
|
372
|
+
end
|
373
|
+
|
374
|
+
#Called when auth is successfully ended
|
375
|
+
# Emits 'auth' and 'ready' event handlers
|
376
|
+
#@param [String] msg Message from JSON-RPC server
|
377
|
+
def on_auth_succeeded(msg)
|
378
|
+
emit('auth', true, msg)
|
379
|
+
emit('ready')
|
380
|
+
end
|
381
|
+
|
382
|
+
#Called when auth is failed
|
383
|
+
# Emits 'auth' event handlers with fail flag
|
384
|
+
#@param [String] msg Message from JSON-RPC server
|
385
|
+
def on_auth_failed(msg)
|
386
|
+
emit('auth', false, msg)
|
387
|
+
end
|
388
|
+
|
389
|
+
|
390
|
+
#~ micro event implementation ~~~~~~~~~~~~~~~~~~~~~~~
|
391
|
+
#
|
392
|
+
|
393
|
+
#Adds ready event handler
|
394
|
+
#@param [Proc] fnReady Event hanlder lambda
|
395
|
+
#@return [Proc] Your event handler
|
396
|
+
def ready(fnReady)
|
397
|
+
return on('ready', fnReady)
|
398
|
+
end
|
399
|
+
|
400
|
+
#Adds any event handler
|
401
|
+
#@param [String] key Event name like `ready`
|
402
|
+
#@param [Proc] fn Function to bind on event
|
403
|
+
#@return [Proc] Your event handler or bindEvent method to bind your handler later if you skip fn
|
404
|
+
def on(key, fn=false)
|
405
|
+
bindEvent = lambda do |fn|
|
406
|
+
@_evtTypeMap.setdefault(key, []) << fn
|
407
|
+
return fn
|
408
|
+
end
|
409
|
+
if not fn
|
410
|
+
return bindEvent
|
411
|
+
else
|
412
|
+
return bindEvent.call(fn)
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
#Calls event handler when event occured
|
417
|
+
#@param [String] key Event name like `ready`
|
418
|
+
#@param [Hash,Array,Float,String,Fixnum] *args Argument to pass to event handler(s).
|
419
|
+
def emit(key, *args)
|
420
|
+
if @_evtTypeMap.has_key? key
|
421
|
+
@_evtTypeMap[key].each do |fn|
|
422
|
+
begin
|
423
|
+
fn.call(self, *args)
|
424
|
+
rescue
|
425
|
+
puts "EMIT exception"
|
426
|
+
end
|
427
|
+
end
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
|
433
|
+
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
434
|
+
#
|
435
|
+
|
436
|
+
|
437
|
+
#Main Bellite class to use
|
438
|
+
class Bellite < BelliteJsonRpc
|
439
|
+
|
440
|
+
|
441
|
+
#!attribute [rw] timeout
|
442
|
+
# Timeout of IO.select socket wait
|
443
|
+
#@return [Float] Timeout value in seconds
|
444
|
+
attr_accessor :timeout
|
445
|
+
|
446
|
+
#Connecting to JSON-RPC server
|
447
|
+
# Calls on_connect if connection successfull
|
448
|
+
#@param [Hash] cred Server credentials: port, host, token and original `credentials` string
|
449
|
+
def _connect(cred)
|
450
|
+
@timeout = 0.5
|
451
|
+
@conn = TCPSocket.new cred['host'], cred['port']
|
452
|
+
@buf = ""
|
453
|
+
|
454
|
+
if @conn
|
455
|
+
on_connect(cred)
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
#Obtains responses from JSON-RPC server
|
460
|
+
#@param [Float] timeout Timeout, if false, @_timeout used
|
461
|
+
def loop(timeout=0.5)
|
462
|
+
if timeout == false
|
463
|
+
timeout = @timeout
|
464
|
+
end
|
465
|
+
Async.loop(timeout, [self])
|
466
|
+
end
|
467
|
+
|
468
|
+
#Sends message to server
|
469
|
+
#@param [String] msg JSON-encoded JSON-RPC call to send
|
470
|
+
#@return [Boolean] True if sent
|
471
|
+
def _sendMessage(msg)
|
472
|
+
if not isConnected?
|
473
|
+
return false
|
474
|
+
end
|
475
|
+
|
476
|
+
@conn.puts(msg + "\0")
|
477
|
+
return true
|
478
|
+
end
|
479
|
+
|
480
|
+
#Checks, is connection still alive
|
481
|
+
#@return [Boolean] True if connection alive
|
482
|
+
def isConnected?
|
483
|
+
return @conn != false
|
484
|
+
end
|
485
|
+
|
486
|
+
#Closing connection to JSON-RPC Server
|
487
|
+
def close()
|
488
|
+
@conn.close
|
489
|
+
@conn = false
|
490
|
+
return true
|
491
|
+
end
|
492
|
+
|
493
|
+
#Returns TCPSocket connection for {Async}
|
494
|
+
#@return [TCPSocket]
|
495
|
+
def fileno()
|
496
|
+
return @conn
|
497
|
+
end
|
498
|
+
|
499
|
+
#Returns is this object readable for {Async}
|
500
|
+
#@return [Boolean]
|
501
|
+
def readable?
|
502
|
+
return true
|
503
|
+
end
|
504
|
+
|
505
|
+
#Returns is this object can read out-of-band data for {Async}
|
506
|
+
#@return [Boolean]
|
507
|
+
def exceptable?
|
508
|
+
return false
|
509
|
+
end
|
510
|
+
|
511
|
+
#Reads new data and runs {#_recvJsonRpc}
|
512
|
+
# This method called by {Async.loop}
|
513
|
+
#@return [Boolean]
|
514
|
+
def handle_read_event()
|
515
|
+
if not isConnected?
|
516
|
+
return false
|
517
|
+
end
|
518
|
+
|
519
|
+
buf = @buf
|
520
|
+
begin
|
521
|
+
while true
|
522
|
+
begin
|
523
|
+
part = @conn.recv_nonblock(4096)
|
524
|
+
rescue IO::WaitReadable
|
525
|
+
break
|
526
|
+
end
|
527
|
+
if not part
|
528
|
+
close()
|
529
|
+
break
|
530
|
+
elsif part == ""
|
531
|
+
break
|
532
|
+
else
|
533
|
+
buf += part
|
534
|
+
end
|
535
|
+
end
|
536
|
+
rescue
|
537
|
+
end
|
538
|
+
|
539
|
+
|
540
|
+
buf = buf.split("\0")
|
541
|
+
_recvJsonRpc(buf)
|
542
|
+
end
|
543
|
+
|
544
|
+
#Returns is this object can write data to socket
|
545
|
+
# This method called by {Async}
|
546
|
+
#@return [Boolean]
|
547
|
+
def writable?()
|
548
|
+
return false
|
549
|
+
end
|
550
|
+
|
551
|
+
#Writes new data to socket
|
552
|
+
# Stub for {Async}
|
553
|
+
def handle_write_event()
|
554
|
+
end
|
555
|
+
|
556
|
+
#Handles out-of-band data
|
557
|
+
# This method can be called by {Async}. Closes connection if called
|
558
|
+
def handle_expt_event()
|
559
|
+
close()
|
560
|
+
end
|
561
|
+
|
562
|
+
#Handles socket closing
|
563
|
+
# This method can be called by {Async}
|
564
|
+
def handle_close()
|
565
|
+
close()
|
566
|
+
end
|
567
|
+
|
568
|
+
#Handles socket error
|
569
|
+
# This method can be called by {Async}. Closing connection
|
570
|
+
def handle_error()
|
571
|
+
close()
|
572
|
+
end
|
573
|
+
end
|
574
|
+
|
575
|
+
#@abstract Promise API
|
576
|
+
class PromiseApi
|
577
|
+
#Runs then-function call with same function for success and failure
|
578
|
+
#@param [Proc] fn Function to handle success or failure
|
579
|
+
#@return Result of then-function call
|
580
|
+
def always(fn)
|
581
|
+
return @_then.call(fn, fn)
|
582
|
+
end
|
583
|
+
|
584
|
+
#Runs then-function in case of failure
|
585
|
+
#@param [Proc] failure Failure handler
|
586
|
+
#@return Result of then-function call
|
587
|
+
def fail(failure)
|
588
|
+
return @_then.call(false, failure)
|
589
|
+
end
|
590
|
+
|
591
|
+
#Runs then-function in case of success
|
592
|
+
#@param [Proc] success Success handler
|
593
|
+
#@return Result of then-function call
|
594
|
+
def done(success)
|
595
|
+
return @_then.call(success,false)
|
596
|
+
end
|
597
|
+
end
|
598
|
+
|
599
|
+
#Class implements promise/future promise
|
600
|
+
class Promise < PromiseApi
|
601
|
+
#Constructing object with then-function
|
602
|
+
#@param [Proc] _then Then-function
|
603
|
+
def initialize(_then)
|
604
|
+
if _then
|
605
|
+
@_then = _then
|
606
|
+
end
|
607
|
+
end
|
608
|
+
|
609
|
+
#Returns this object
|
610
|
+
#@return [Promise]
|
611
|
+
def promise
|
612
|
+
return self
|
613
|
+
end
|
614
|
+
|
615
|
+
#@!attribute [r] _then
|
616
|
+
# Then-function
|
617
|
+
#@return [Proc,lambda]
|
618
|
+
def _then
|
619
|
+
@_then
|
620
|
+
end
|
621
|
+
end
|
622
|
+
|
623
|
+
#Implements Future
|
624
|
+
class Future < PromiseApi
|
625
|
+
#Constructing object with then, success and failure functions
|
626
|
+
#@param [Proc,lambda] _then Then-function
|
627
|
+
#@param [Proc,lambda] resolve Success-function
|
628
|
+
#@param [Proc,lambda] reject Failure-function
|
629
|
+
def initialize(_then, resolve=false, reject=false)
|
630
|
+
@promise = Promise.new _then
|
631
|
+
if resolve
|
632
|
+
@resolve = resolve
|
633
|
+
end
|
634
|
+
if reject
|
635
|
+
@reject = reject
|
636
|
+
end
|
637
|
+
end
|
638
|
+
|
639
|
+
|
640
|
+
#@!attribute [r] resolve
|
641
|
+
# Success-function
|
642
|
+
#@return [Proc,lambda]
|
643
|
+
def resolve
|
644
|
+
@resolve
|
645
|
+
end
|
646
|
+
|
647
|
+
#@!attribute [r] reject
|
648
|
+
# Failure-function
|
649
|
+
#@return [Proc,lambda]
|
650
|
+
def reject
|
651
|
+
@reject
|
652
|
+
end
|
653
|
+
|
654
|
+
#@!attribute [r] promise
|
655
|
+
# Promise object of this Future
|
656
|
+
#@return [Promise]
|
657
|
+
def promise
|
658
|
+
@promise
|
659
|
+
end
|
660
|
+
end
|
661
|
+
|
662
|
+
#Creates Future object for JSON-RPC Server response
|
663
|
+
#@return [Future]
|
664
|
+
def deferred()
|
665
|
+
cb = []
|
666
|
+
answer = false
|
667
|
+
|
668
|
+
future = false
|
669
|
+
reject = false
|
670
|
+
|
671
|
+
_then = lambda do |*args|
|
672
|
+
success = args[0] || false
|
673
|
+
failure = args[1] || false
|
674
|
+
cb << [success,failure]
|
675
|
+
if answer
|
676
|
+
answer.call
|
677
|
+
end
|
678
|
+
return future.promise
|
679
|
+
end
|
680
|
+
|
681
|
+
resolve = lambda do |result|
|
682
|
+
while cb.size > 0
|
683
|
+
success, failure = cb.pop()
|
684
|
+
begin
|
685
|
+
if success != false
|
686
|
+
res = success.call(result)
|
687
|
+
if res != false
|
688
|
+
result = res
|
689
|
+
end
|
690
|
+
end
|
691
|
+
rescue Exception => err
|
692
|
+
if failure != false
|
693
|
+
res = failure.call(err)
|
694
|
+
elsif cb.size = 0
|
695
|
+
#excepthook
|
696
|
+
end
|
697
|
+
if res == false
|
698
|
+
return reject.call(err)
|
699
|
+
else
|
700
|
+
return reject.call(res)
|
701
|
+
end
|
702
|
+
end
|
703
|
+
end
|
704
|
+
answer = partial(resolve, result)
|
705
|
+
end
|
706
|
+
|
707
|
+
reject = lambda do |error|
|
708
|
+
while cb.size > 0
|
709
|
+
failure = cb.pop()[1]
|
710
|
+
begin
|
711
|
+
if failure != false
|
712
|
+
res = failure.call(error)
|
713
|
+
if res != false
|
714
|
+
error = res
|
715
|
+
end
|
716
|
+
end
|
717
|
+
rescue Exception => err
|
718
|
+
res = err
|
719
|
+
if cb.size == 0
|
720
|
+
#excepthook
|
721
|
+
end
|
722
|
+
end
|
723
|
+
end
|
724
|
+
answer = partial(reject, error)
|
725
|
+
end
|
726
|
+
|
727
|
+
future = Future.new _then, resolve, reject
|
728
|
+
return future
|
729
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require '../bellite.rb'
|
4
|
+
app = Bellite.new
|
5
|
+
app.ready Proc.new {
|
6
|
+
puts "READY"
|
7
|
+
app.ping
|
8
|
+
app.version
|
9
|
+
app.perform(142, "echo", {"name" => [nil, true, 42, "value"]})
|
10
|
+
|
11
|
+
app.bindEvent(118, "*")
|
12
|
+
app.unbindEvent(118, "*")
|
13
|
+
|
14
|
+
app.on("testEvent", lambda { |app, eobj|
|
15
|
+
puts "TEST EVENT"
|
16
|
+
puts eobj
|
17
|
+
if eobj['evt']
|
18
|
+
app.perform(0, eobj['evt'])
|
19
|
+
else
|
20
|
+
app.close
|
21
|
+
end
|
22
|
+
})
|
23
|
+
|
24
|
+
app.bindEvent(0, "testEvent", 42, {'testCtx' => true})
|
25
|
+
app.perform(0, "testEvent")
|
26
|
+
}
|
27
|
+
|
28
|
+
app.timeout = false
|
29
|
+
app.loop false
|
@@ -0,0 +1,264 @@
|
|
1
|
+
/*-*- coding: utf-8 -*- vim: set ts=4 sw=4 expandtab
|
2
|
+
##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
|
3
|
+
##~ Copyright (C) 2002-2012 Bellite.io ##
|
4
|
+
##~ ##
|
5
|
+
##~ This library is free software; you can redistribute it ##
|
6
|
+
##~ and/or modify it under the terms of the MIT style License as ##
|
7
|
+
##~ found in the LICENSE file included with this distribution. ##
|
8
|
+
##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##*/
|
9
|
+
|
10
|
+
"use strict";
|
11
|
+
function createMockBelliteServer(ns) {
|
12
|
+
var self = {
|
13
|
+
server: require('net').createServer(),
|
14
|
+
token: ns.token || require('crypto').randomBytes(8).toString('hex'),
|
15
|
+
shutdownTest: function() {
|
16
|
+
self.shutdownTest = null;
|
17
|
+
if (self.server)
|
18
|
+
self.server.close();
|
19
|
+
self.allConnections.forEach(function(conn){
|
20
|
+
if (conn) conn.end() }) },
|
21
|
+
allConnections: [] }
|
22
|
+
|
23
|
+
var debugLog = ns.debugLog
|
24
|
+
|
25
|
+
self.server.on('listening', function () {
|
26
|
+
var addr = this.address()
|
27
|
+
self.env = addr.address+':'+addr.port+'/'+self.token
|
28
|
+
process.env.BELLITE_SERVER = self.env
|
29
|
+
if (debugLog) debugLog('Serving BELLITE_SERVER="'+ process.env.BELLITE_SERVER +'"')
|
30
|
+
ns.listening();
|
31
|
+
})
|
32
|
+
|
33
|
+
self.server.on('connection', function (conn) {
|
34
|
+
self.allConnections.push(conn)
|
35
|
+
conn.setEncoding("UTF-8");
|
36
|
+
conn.setNoDelay(true);
|
37
|
+
conn.setKeepAlive(true, 0);
|
38
|
+
|
39
|
+
var api = {
|
40
|
+
sendMessage: function(msg) {
|
41
|
+
if (debugLog) debugLog('reply ==> ', msg);
|
42
|
+
return conn.write(msg+'\0') },
|
43
|
+
shutdown: function() { return conn.end() },
|
44
|
+
fireEvent: function(evtType, selfId, evt, ctx) {
|
45
|
+
return this.send('event', {evtType: evtType, selfId: selfId||0, evt:evt, ctx:ctx}) },
|
46
|
+
send: function(method, params, id) {
|
47
|
+
var msg = {jsonrpc: "2.0", id:id, method:method, params:params}
|
48
|
+
return this.sendMessage(JSON.stringify(msg)) },
|
49
|
+
answer: function(result, id) {
|
50
|
+
if (id===undefined) return false;
|
51
|
+
var msg = {jsonrpc: "2.0", id:id, result:result}
|
52
|
+
return this.sendMessage(JSON.stringify(msg)) },
|
53
|
+
error: function(error, id) {
|
54
|
+
if (id===undefined) return false;
|
55
|
+
var msg = {jsonrpc: "2.0", id:id, error:error}
|
56
|
+
return this.sendMessage(JSON.stringify(msg)) },
|
57
|
+
checkAuth: function(token) {
|
58
|
+
clearTimeout(authTimeout);
|
59
|
+
return this.authorized = (token == self.token) }
|
60
|
+
},
|
61
|
+
tgt = ns.connect(api),
|
62
|
+
authTimeout = setTimeout(api.shutdown, 250);
|
63
|
+
|
64
|
+
var connBuf='';
|
65
|
+
conn.on('data', function(data) {
|
66
|
+
data = (connBuf+data).split('\0')
|
67
|
+
connBuf = data.pop()
|
68
|
+
while (data.length) {
|
69
|
+
var msg = data.shift();
|
70
|
+
if (debugLog) debugLog('invoke <== ', msg);
|
71
|
+
try { msg = JSON.parse(msg) }
|
72
|
+
catch (err) { tgt.parse_error(err, msg); continue }
|
73
|
+
if (msg.method!==undefined)
|
74
|
+
tgt.invoke(msg)
|
75
|
+
else tgt.response(msg)
|
76
|
+
} })
|
77
|
+
conn.on('close', function() { tgt.conn_close() })
|
78
|
+
conn.on('error', function(err) { tgt.conn_error(err) })
|
79
|
+
})
|
80
|
+
|
81
|
+
self.server.on('close', function() {
|
82
|
+
self.server = null; ns.server_close() })
|
83
|
+
self.server.on('error', function(err) {
|
84
|
+
ns.server_error(err) })
|
85
|
+
|
86
|
+
self.server.listen(ns.port || 0, '127.0.0.1')
|
87
|
+
return self;
|
88
|
+
}
|
89
|
+
exports.createMockBelliteServer = createMockBelliteServer;
|
90
|
+
|
91
|
+
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
92
|
+
|
93
|
+
function testBelliteJSONRPC(opt, doneCallback) {
|
94
|
+
opt.log = opt.log || {};
|
95
|
+
function log(key, args) {
|
96
|
+
var log=opt.log[key];
|
97
|
+
if (!log) opt.log[key] = log = []
|
98
|
+
var ea = Array.prototype.slice.call(arguments, 1);
|
99
|
+
if (ea.length<2) ea = ea[0]===undefined ? 0 : ea[0];
|
100
|
+
log.push(ea)
|
101
|
+
return this }
|
102
|
+
|
103
|
+
var rpc = {
|
104
|
+
connect: function(api) {
|
105
|
+
log('connect')
|
106
|
+
this.api = api;
|
107
|
+
this.evtCtx = {}
|
108
|
+
return this },
|
109
|
+
conn_close: function() { log('conn_close') },
|
110
|
+
conn_error: function(err) { log('conn_error', err) },
|
111
|
+
parse_error: function(err,msg) { log('parse_error', err, msg) },
|
112
|
+
response: function(msg) { log('response', msg) },
|
113
|
+
invoke: function(msg) {
|
114
|
+
var pre = this.api.authorized ? 'meth_' : 'meth0_';
|
115
|
+
var meth = this[pre+msg.method] || this.meth_unknown;
|
116
|
+
log('invoke', pre+msg.method, msg.params)
|
117
|
+
return meth.call(this, msg.params, msg)},
|
118
|
+
|
119
|
+
meth_unknown: function(args, msg) {
|
120
|
+
log('meth_unknown', msg.method, args)
|
121
|
+
this.api.answer(["unknown method ", null], msg.id) },
|
122
|
+
meth_ping: function(args, msg) {
|
123
|
+
log('meth_ping', args)
|
124
|
+
this.api.answer([null, true, "pong"], msg.id) },
|
125
|
+
meth_version: function(args, msg) {
|
126
|
+
log('meth_version', args)
|
127
|
+
this.api.answer([null, {"server":"bellite", "version":"1.4.3", "platform":"node/test"}], msg.id) },
|
128
|
+
|
129
|
+
meth0_ping: function(args, msg) { this.meth_ping(args, msg) },
|
130
|
+
meth0_version: function(args, msg) { this.meth_version(args, msg) },
|
131
|
+
meth0_auth: function(args, msg) {
|
132
|
+
log('meth_auth', args)
|
133
|
+
if (this.api.checkAuth(args[0])) {
|
134
|
+
log('authorize', true)
|
135
|
+
this.api.answer([null, true, "authorized"], msg.id)
|
136
|
+
} else {
|
137
|
+
log('authorize', false)
|
138
|
+
this.api.error({code:401, message:"Unauthorized"}, msg.id)
|
139
|
+
this.api.shutdown()
|
140
|
+
} },
|
141
|
+
|
142
|
+
meth_bindEvent: function(args, msg) {
|
143
|
+
log('meth_bindEvent', args)
|
144
|
+
this.evtCtx[args[1]] = args[3]
|
145
|
+
this.api.answer([null, true], msg.id) },
|
146
|
+
meth_unbindEvent: function(args, msg) {
|
147
|
+
log('meth_unbindEvent', args)
|
148
|
+
this.api.answer([null, true], msg.id) },
|
149
|
+
meth_perform: function(args, msg) {
|
150
|
+
log('meth_perform', args)
|
151
|
+
var self=this, cmd=this['cmd_'+args[1]] || this.cmd_mock
|
152
|
+
cmd.call(this, args, function(res) {
|
153
|
+
self.api.answer([null, res], msg.id) }) },
|
154
|
+
|
155
|
+
cmd_mock: function(args, answer) {
|
156
|
+
answer([null, {'a mock':'result'}]) },
|
157
|
+
cmd_testEvent: function(args, answer) {
|
158
|
+
log('cmd_testEvent', args)
|
159
|
+
answer([null, true, 'firingTestEvent'])
|
160
|
+
var resp = 'dyn_'+require('crypto').randomBytes(4).toString('hex')
|
161
|
+
this['cmd_'+resp] = this.dynamic_response
|
162
|
+
this.api.fireEvent('testEvent', 0, resp, this.evtCtx.testEvent) },
|
163
|
+
dynamic_response: function(args, answer) {
|
164
|
+
log('dynamic_response', args)
|
165
|
+
answer([null, true, 'awesome'])
|
166
|
+
this.api.fireEvent('testEvent', 0, null, this.evtCtx.testEvent) }
|
167
|
+
};
|
168
|
+
|
169
|
+
var test = createMockBelliteServer({
|
170
|
+
port: opt.port, token: opt.token, debugLog: opt.debugLog,
|
171
|
+
listening: function() {
|
172
|
+
opt.execClient(spawnClient) },
|
173
|
+
server_close: function() {},
|
174
|
+
server_error: function(err) { log('server_error', err) },
|
175
|
+
connect: function(api) {
|
176
|
+
return Object.create(rpc).connect(api) }
|
177
|
+
})
|
178
|
+
|
179
|
+
function spawnClient(exec, args) {
|
180
|
+
var cp = require('child_process')
|
181
|
+
test.proc = cp.spawn(exec, args, {stdio:'inherit',cwd:__dirname})
|
182
|
+
test.proc.on('exit', function(code, signal) {
|
183
|
+
log('process_exit', code, signal)
|
184
|
+
test.proc = false;
|
185
|
+
done(code!==0 ? 'subprocess spawning error' : null) })
|
186
|
+
return test.proc }
|
187
|
+
|
188
|
+
function done(err) {
|
189
|
+
if (done.timer)
|
190
|
+
clearTimeout(done.timer)
|
191
|
+
|
192
|
+
if (test.proc) {
|
193
|
+
test.proc.kill()
|
194
|
+
test.proc = null
|
195
|
+
}
|
196
|
+
if (test.shutdownTest) {
|
197
|
+
test.shutdownTest()
|
198
|
+
setTimeout(function() { doneCallback(err, opt.log, opt) }, 100)
|
199
|
+
}}
|
200
|
+
if (opt.timeout!==false)
|
201
|
+
done.timer = setTimeout(function() {
|
202
|
+
log('timeout'); done('timeout') }, opt.timeout || 2000)
|
203
|
+
}
|
204
|
+
exports.testBelliteJSONRPC = testBelliteJSONRPC;
|
205
|
+
|
206
|
+
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
207
|
+
|
208
|
+
function assetTestResults(err, log, opt) {
|
209
|
+
var assert=require('assert');
|
210
|
+
try {
|
211
|
+
assert.equal(log.connect && log.connect.length, 1, "should connect exactly once")
|
212
|
+
assert.equal(log.server_error, null, "should not experience server errors")
|
213
|
+
assert.equal(log.conn_error, null, "should not experience connection errors")
|
214
|
+
|
215
|
+
assert.equal(log.parse_error, null, "should not experience JSON parsing errors")
|
216
|
+
assert.equal(log.response, null, "should not receive JSON-RPC 2.0 responses")
|
217
|
+
|
218
|
+
assert.equal(log.meth_auth && log.meth_auth.length, 1, "should call auth exactly once")
|
219
|
+
assert.deepEqual(log.authorize, [true], "should authorize successfully")
|
220
|
+
|
221
|
+
assert.equal(log.meth_unknown, null, "should never call an unknown method")
|
222
|
+
|
223
|
+
assert.deepEqual(log.meth_ping, [0], "should call ping once with no args")
|
224
|
+
assert.deepEqual(log.meth_version, [0], "should call version once with no args")
|
225
|
+
assert.equal(log.meth_perform && log.meth_perform.length, 3, "should call perform 3 times")
|
226
|
+
assert.equal(log.meth_bindEvent && log.meth_bindEvent.length, 2, "should call bindEvent 3 times")
|
227
|
+
assert.equal(log.meth_unbindEvent && log.meth_unbindEvent.length, 1, "should call unbindEvent 2 times")
|
228
|
+
|
229
|
+
assert.deepEqual(log.cmd_testEvent, [[0, 'testEvent', null]], "should call testEvent")
|
230
|
+
assert.equal(log.dynamic_response && log.dynamic_response.length, 1, "should call dynamic_response from event")
|
231
|
+
|
232
|
+
assert.equal(err, null, "terminated with an error")
|
233
|
+
|
234
|
+
console.log("All Bellite JSON-RPC protocol tests passed")
|
235
|
+
process.exit(0) // success
|
236
|
+
|
237
|
+
} catch(test_err) {
|
238
|
+
console.log('\nBellite Mock Server Call Log:')
|
239
|
+
console.log(log)
|
240
|
+
console.log('\n')
|
241
|
+
|
242
|
+
console.error(test_err.stack.split(/\n\s*/).slice(0,2).join('\n'))
|
243
|
+
console.error(test_err)
|
244
|
+
console.error()
|
245
|
+
|
246
|
+
process.exit(1) // failure
|
247
|
+
}
|
248
|
+
}
|
249
|
+
exports.assetTestResults = assetTestResults;
|
250
|
+
|
251
|
+
if (!module.parent) {
|
252
|
+
// test the bellist JSON-RPC interactions
|
253
|
+
testBelliteJSONRPC({
|
254
|
+
debugLog: console.log,
|
255
|
+
timeout: 2000,
|
256
|
+
//timeout: false,
|
257
|
+
//port: 3099,
|
258
|
+
//token: 'bellite-demo-host',
|
259
|
+
|
260
|
+
execClient: function(spawn) {
|
261
|
+
spawn('ruby', [__dirname+'/_doBelliteTest.rb'])
|
262
|
+
}
|
263
|
+
}, assetTestResults)
|
264
|
+
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*- vim: set ts=4 sw=4 expandtab
|
3
|
+
##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
|
4
|
+
##~ Copyright (C) 2002-2012 Bellite.io ##
|
5
|
+
##~ ##
|
6
|
+
##~ This library is free software; you can redistribute it ##
|
7
|
+
##~ and/or modify it under the terms of the MIT style License as ##
|
8
|
+
##~ found in the LICENSE file included with this distribution. ##
|
9
|
+
##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
|
10
|
+
|
11
|
+
import unittest, os, sys
|
12
|
+
|
13
|
+
class TestJSONRPC(unittest.TestCase):
|
14
|
+
def testBelliteClient(self):
|
15
|
+
src = os.path.splitext(__file__)[0]+'.js'
|
16
|
+
assert 0==os.system('node '+src)
|
17
|
+
|
18
|
+
|
19
|
+
if __name__=='__main__':
|
20
|
+
unittest.main()
|
21
|
+
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bellite
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Ivan
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-12-01 00:00:00 Z
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: Bellite JSON-RPC Client library
|
22
|
+
email:
|
23
|
+
- asmer@asmer.org.ua
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files: []
|
29
|
+
|
30
|
+
files:
|
31
|
+
- .gitignore
|
32
|
+
- Gemfile
|
33
|
+
- LICENSE
|
34
|
+
- README.md
|
35
|
+
- Rakefile
|
36
|
+
- bellite.gemspec
|
37
|
+
- lib/bellite.rb
|
38
|
+
- lib/bellite/version.rb
|
39
|
+
- test/_doBelliteTest.rb
|
40
|
+
- test/testClientJSONRPC.js
|
41
|
+
- test/testClientJSONRPC.py
|
42
|
+
homepage: ""
|
43
|
+
licenses: []
|
44
|
+
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
hash: 3
|
56
|
+
segments:
|
57
|
+
- 0
|
58
|
+
version: "0"
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
hash: 3
|
65
|
+
segments:
|
66
|
+
- 0
|
67
|
+
version: "0"
|
68
|
+
requirements: []
|
69
|
+
|
70
|
+
rubyforge_project:
|
71
|
+
rubygems_version: 1.8.24
|
72
|
+
signing_key:
|
73
|
+
specification_version: 3
|
74
|
+
summary: Implements connection to JSON-RPC server, calling remote methods and bindings to server events
|
75
|
+
test_files:
|
76
|
+
- test/_doBelliteTest.rb
|
77
|
+
- test/testClientJSONRPC.js
|
78
|
+
- test/testClientJSONRPC.py
|
79
|
+
has_rdoc:
|