bellite 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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:
|