rinterface 0.0.3
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 +4 -0
- data/README.md +111 -0
- data/Rakefile +107 -0
- data/VERSION +1 -0
- data/examples/client.rb +19 -0
- data/examples/math_server.erl +28 -0
- data/lib/rinterface/epmd.rb +58 -0
- data/lib/rinterface/erl.rb +24 -0
- data/lib/rinterface/erlang/decoder.rb +223 -0
- data/lib/rinterface/erlang/encoder.rb +107 -0
- data/lib/rinterface/erlang/external_format.rb +28 -0
- data/lib/rinterface/erlang/types.rb +37 -0
- data/lib/rinterface/node.rb +242 -0
- data/lib/rinterface.rb +13 -0
- data/rinterface.gemspec +71 -0
- data/spec/rinterface/erl_spec.rb +24 -0
- data/spec/rinterface/erlang/external_format_spec.rb +5 -0
- data/spec/rinterface/node_spec.rb +5 -0
- data/spec/rinterface_spec.rb +134 -0
- data/spec/spec_helper.rb +63 -0
- data/spec/spec_server.erl +29 -0
- metadata +112 -0
@@ -0,0 +1,37 @@
|
|
1
|
+
module Erlang
|
2
|
+
module External
|
3
|
+
module Types
|
4
|
+
SMALL_INT = 97
|
5
|
+
INT = 98
|
6
|
+
|
7
|
+
SMALL_BIGNUM = 110
|
8
|
+
LARGE_BIGNUM = 111
|
9
|
+
|
10
|
+
FLOAT = 99
|
11
|
+
NEW_FLOAT = 70
|
12
|
+
|
13
|
+
ATOM = 100
|
14
|
+
REF = 101 #old style reference
|
15
|
+
NEW_REF = 114
|
16
|
+
PORT = 102 #not supported accross node boundaries
|
17
|
+
PID = 103
|
18
|
+
|
19
|
+
SMALL_TUPLE = 104
|
20
|
+
LARGE_TUPLE = 105
|
21
|
+
|
22
|
+
NIL = 106
|
23
|
+
STRING = 107
|
24
|
+
LIST = 108
|
25
|
+
BIN = 109
|
26
|
+
|
27
|
+
FUN = 117
|
28
|
+
NEW_FUN = 112
|
29
|
+
end
|
30
|
+
|
31
|
+
VERSION = 131
|
32
|
+
|
33
|
+
MAX_INT = (1 << 27) -1
|
34
|
+
MIN_INT = -(1 << 27)
|
35
|
+
MAX_ATOM = 255
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,242 @@
|
|
1
|
+
#
|
2
|
+
# Single shot client.
|
3
|
+
# - connects to epmd to get the port number of the erlang node
|
4
|
+
# - does the handshake
|
5
|
+
# - makes the RPC call
|
6
|
+
#
|
7
|
+
module Erlang
|
8
|
+
class Node
|
9
|
+
attr_reader :result
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@result = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def fun(node, mod, fun, *args)
|
17
|
+
rpc(node, mod, fun, args)
|
18
|
+
end
|
19
|
+
|
20
|
+
def rpc(node, mod, fun, args)
|
21
|
+
n = self.new
|
22
|
+
setup = proc{ n.do_connect(node, mod, fun, args) }
|
23
|
+
|
24
|
+
if EM.reactor_running?
|
25
|
+
setup.call
|
26
|
+
else
|
27
|
+
EM.run(&setup)
|
28
|
+
end
|
29
|
+
n.result
|
30
|
+
end
|
31
|
+
#alias :fun :rpc
|
32
|
+
end
|
33
|
+
|
34
|
+
def do_connect(node,mod,fun,args)
|
35
|
+
epmd = EpmdConnection.lookup_node(node)
|
36
|
+
epmd.callback do |port|
|
37
|
+
conn = NodeConnection.rpc_call(node,port,mod,fun,args) do |c|
|
38
|
+
c.destnode = node
|
39
|
+
c.mod = mod
|
40
|
+
c.fun = fun
|
41
|
+
c.args = args
|
42
|
+
c.port = port
|
43
|
+
c.myname = build_nodename
|
44
|
+
c.cookie = get_cookie
|
45
|
+
end
|
46
|
+
conn.callback do |r|
|
47
|
+
# Check for bad rpc response
|
48
|
+
# this is where I miss the patten matching in Erlang
|
49
|
+
if r.is_a?(Array)
|
50
|
+
if !r.empty? && r[0] == :badrpc
|
51
|
+
@result = [:badrpc,r[1]]
|
52
|
+
else
|
53
|
+
@result = [:ok,r]
|
54
|
+
end
|
55
|
+
else
|
56
|
+
@result = [:ok,r]
|
57
|
+
end
|
58
|
+
EM.stop
|
59
|
+
end
|
60
|
+
conn.errback do |err|
|
61
|
+
# never called??
|
62
|
+
@result = [:badrpc,err]
|
63
|
+
EM.stop
|
64
|
+
end
|
65
|
+
end
|
66
|
+
epmd.errback do |err|
|
67
|
+
# return bad RPC no port found (0)
|
68
|
+
@result = [:badrpc,"no port found for service"]
|
69
|
+
EM.stop
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
class NodeConnection < EM::Connection
|
77
|
+
include EM::Deferrable
|
78
|
+
|
79
|
+
attr_accessor :host,:myname,:destnode,:port,:cookie,:mod,:fun,:args
|
80
|
+
|
81
|
+
# node = destination node
|
82
|
+
# port = the port of the Erlang node (from epmd)
|
83
|
+
# mod = the module to call
|
84
|
+
# fun = the function to call
|
85
|
+
# args = args to pass to fun
|
86
|
+
def self.rpc_call(node,port,mod,fun,args)
|
87
|
+
EM.connect("127.0.0.1",port,self) do |c|
|
88
|
+
c.destnode = node
|
89
|
+
c.mod = mod
|
90
|
+
c.fun = fun
|
91
|
+
c.args = args
|
92
|
+
c.port = port
|
93
|
+
c.myname = build_nodename
|
94
|
+
c.cookie = get_cookie
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
# Get the Cookie from the home directory
|
100
|
+
def self.get_cookie
|
101
|
+
# ... I did it all for the cookie, come on the cookie ...
|
102
|
+
fp = File.expand_path("~#{ENV['USER']}/.erlang.cookie")
|
103
|
+
fh = File.open(fp,'r')
|
104
|
+
fh.readline.strip
|
105
|
+
end
|
106
|
+
|
107
|
+
# Build a nodename for us
|
108
|
+
def self.build_nodename
|
109
|
+
require 'socket'
|
110
|
+
myhostname = Socket.gethostname.split(".")[0]
|
111
|
+
"ruby_client@#{myhostname}"
|
112
|
+
end
|
113
|
+
|
114
|
+
def post_init
|
115
|
+
@responder = :determine_message
|
116
|
+
end
|
117
|
+
|
118
|
+
def connection_completed
|
119
|
+
send_data send_name
|
120
|
+
end
|
121
|
+
|
122
|
+
def receive_data data
|
123
|
+
@resp = data
|
124
|
+
send @responder
|
125
|
+
end
|
126
|
+
|
127
|
+
def handle_any_response
|
128
|
+
result = ""
|
129
|
+
decoder = Decode.read_bits(@resp)
|
130
|
+
s = decoder.read_4
|
131
|
+
#puts "Size: #{s}"
|
132
|
+
code = decoder.read_string(1)
|
133
|
+
if code == 'p'
|
134
|
+
#puts "found the p"
|
135
|
+
# read the control message and ignore
|
136
|
+
decoder.read_any
|
137
|
+
# read the message
|
138
|
+
result = decoder.read_any
|
139
|
+
#puts "Raw Response: #{result.inspect}"
|
140
|
+
set_deferred_success result[1]
|
141
|
+
else
|
142
|
+
# This seems to never happen...always 'p'
|
143
|
+
result = decoder.read_any
|
144
|
+
set_deferred_failure result
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def send_name
|
149
|
+
full_host_name = self.myname
|
150
|
+
encode = Encoder.new
|
151
|
+
encode.write_2(full_host_name.length + 7)
|
152
|
+
# node type
|
153
|
+
encode.write_1(110)
|
154
|
+
# distChoose
|
155
|
+
encode.write_2(5)
|
156
|
+
# flags
|
157
|
+
encode.write_4(4|256|1024|2048)
|
158
|
+
# node name
|
159
|
+
encode.write_string(full_host_name)
|
160
|
+
encode.out.string
|
161
|
+
end
|
162
|
+
|
163
|
+
def determine_message
|
164
|
+
decoder = Decode.read_bits(@resp)
|
165
|
+
packet_size = decoder.read_2
|
166
|
+
#puts "PacketSize: #{packet_size}"
|
167
|
+
status_code = decoder.read_string(1)
|
168
|
+
case status_code
|
169
|
+
when 's' then receive_status(packet_size,decoder)
|
170
|
+
when 'n' then receive_challenge(packet_size,decoder)
|
171
|
+
when 'a' then receive_challenge_ack(packet_size,decoder)
|
172
|
+
else "Got back a weird packet"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def receive_status(packet_size,decoder)
|
177
|
+
status = decoder.read_string(packet_size-1)
|
178
|
+
if decoder.in.size > (packet_size + 2)
|
179
|
+
# Hack when both receive packets are crammed together into 1
|
180
|
+
next_packet_size = decoder.read_2 # read size
|
181
|
+
status_code = decoder.read_string(1)
|
182
|
+
receive_challenge(next_packet_size, decoder)
|
183
|
+
end
|
184
|
+
set_deferred_failure "Failed on Recv Status: #{status_code}" unless status == 'ok'
|
185
|
+
end
|
186
|
+
|
187
|
+
def receive_challenge(packet_size,decoder)
|
188
|
+
dist_code = decoder.read_2
|
189
|
+
#puts "Code: #{dist_code}"
|
190
|
+
flags = decoder.read_4
|
191
|
+
#puts "Flags #{flags}"
|
192
|
+
challenge = decoder.read_4
|
193
|
+
his_name = decoder.read_string(decoder.in.size-13)
|
194
|
+
#puts "His name: #{his_name}"
|
195
|
+
#puts "Got the challenge #{challenge}"
|
196
|
+
#out_challenge = make_challenge(challenge)
|
197
|
+
send_data make_challenge(challenge)
|
198
|
+
end
|
199
|
+
|
200
|
+
def make_challenge(her_challenge)
|
201
|
+
incr_digest = Digest::MD5.new()
|
202
|
+
incr_digest << @cookie
|
203
|
+
incr_digest << her_challenge.to_s
|
204
|
+
digest = incr_digest.digest
|
205
|
+
our_challenge = rand(10000)
|
206
|
+
encoder = Encoder.new
|
207
|
+
encoder.write_2(21)
|
208
|
+
encoder.write_string('r')
|
209
|
+
encoder.write_4(our_challenge)
|
210
|
+
encoder.write_string(digest)
|
211
|
+
encoder.out.string
|
212
|
+
end
|
213
|
+
|
214
|
+
# Handshake complete...send the RPC
|
215
|
+
def receive_challenge_ack(packet_size,decoder)
|
216
|
+
@responder = :handle_any_response
|
217
|
+
call_remote
|
218
|
+
end
|
219
|
+
|
220
|
+
def call_remote
|
221
|
+
myPid = Erlang::Terms::Pid.new(self.myname.intern,5,5,5)
|
222
|
+
call_tuple = [:call,self.mod.intern,self.fun.intern,Erlang::Terms::List.new(self.args),:user]
|
223
|
+
rpc_tuple = [myPid,call_tuple]
|
224
|
+
ctl_msg = [6,myPid,self.cookie.intern,:rex]
|
225
|
+
|
226
|
+
encode_data = Encoder.new
|
227
|
+
encode_data.term_to_binary(ctl_msg)
|
228
|
+
encode_data.term_to_binary(rpc_tuple)
|
229
|
+
data = encode_data.out.string
|
230
|
+
|
231
|
+
f = Encoder.new
|
232
|
+
f.write_4(data.length + 1)
|
233
|
+
final_out = f.out.string + 'p' + data
|
234
|
+
send_data final_out
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
|
241
|
+
|
242
|
+
|
data/lib/rinterface.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
require "rubygems"
|
5
|
+
require "eventmachine"
|
6
|
+
require "digest/md5"
|
7
|
+
require "rinterface/erlang/external_format"
|
8
|
+
require "rinterface/erlang/types"
|
9
|
+
require "rinterface/erlang/encoder"
|
10
|
+
require "rinterface/erlang/decoder"
|
11
|
+
require "rinterface/epmd"
|
12
|
+
require "rinterface/node"
|
13
|
+
require "rinterface/erl"
|
data/rinterface.gemspec
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{rinterface}
|
8
|
+
s.version = "0.0.3"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Dave Bryson", "\303\211verton Ribeiro", "Marcos Piccinini"]
|
12
|
+
s.date = %q{2010-03-31}
|
13
|
+
s.description = %q{Pure Ruby client that can send RPC calls to an Erlang node}
|
14
|
+
s.email = %q{r@interf.ace}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.md"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".gitignore",
|
20
|
+
"README.md",
|
21
|
+
"Rakefile",
|
22
|
+
"VERSION",
|
23
|
+
"examples/client.rb",
|
24
|
+
"examples/math_server.erl",
|
25
|
+
"lib/rinterface.rb",
|
26
|
+
"lib/rinterface/epmd.rb",
|
27
|
+
"lib/rinterface/erl.rb",
|
28
|
+
"lib/rinterface/erlang/decoder.rb",
|
29
|
+
"lib/rinterface/erlang/encoder.rb",
|
30
|
+
"lib/rinterface/erlang/external_format.rb",
|
31
|
+
"lib/rinterface/erlang/types.rb",
|
32
|
+
"lib/rinterface/node.rb",
|
33
|
+
"rinterface.gemspec",
|
34
|
+
"spec/rinterface/erl_spec.rb",
|
35
|
+
"spec/rinterface/erlang/external_format_spec.rb",
|
36
|
+
"spec/rinterface/node_spec.rb",
|
37
|
+
"spec/rinterface_spec.rb",
|
38
|
+
"spec/spec_helper.rb",
|
39
|
+
"spec/spec_server.erl"
|
40
|
+
]
|
41
|
+
s.homepage = %q{http://github.com/davebryson/rinterface}
|
42
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
43
|
+
s.require_paths = ["lib"]
|
44
|
+
s.rubygems_version = %q{1.3.6}
|
45
|
+
s.summary = %q{Erlang RPC Ruby Client}
|
46
|
+
s.test_files = [
|
47
|
+
"spec/rinterface_spec.rb",
|
48
|
+
"spec/spec_helper.rb",
|
49
|
+
"spec/rinterface/node_spec.rb",
|
50
|
+
"spec/rinterface/erl_spec.rb",
|
51
|
+
"spec/rinterface/erlang/external_format_spec.rb",
|
52
|
+
"examples/client.rb"
|
53
|
+
]
|
54
|
+
|
55
|
+
if s.respond_to? :specification_version then
|
56
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
57
|
+
s.specification_version = 3
|
58
|
+
|
59
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
60
|
+
s.add_runtime_dependency(%q<eventmachine>, [">= 0"])
|
61
|
+
s.add_development_dependency(%q<rspec>, [">= 0"])
|
62
|
+
else
|
63
|
+
s.add_dependency(%q<eventmachine>, [">= 0"])
|
64
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
65
|
+
end
|
66
|
+
else
|
67
|
+
s.add_dependency(%q<eventmachine>, [">= 0"])
|
68
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
include Rinterface
|
3
|
+
|
4
|
+
describe Erl do
|
5
|
+
|
6
|
+
it "should shotcut method calling" do
|
7
|
+
Erl::SpecServer.echo(:spec, "Rock").should eql([:ok, "Rock"])
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should shotcut method calling" do
|
11
|
+
Erl::SpecServer.power(:spec, 10).should eql([:ok, 100])
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should shotcut method calling" do
|
15
|
+
Erl::SpecServer.add(:spec, 2, 2).should be_ok_with(4)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should snake case" do
|
19
|
+
Erl.snake("SpecServer").should eql("spec_server")
|
20
|
+
Erl.snake("Fu").should eql("fu")
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "Rinterface" do
|
4
|
+
|
5
|
+
describe "Erlang Spec Server" do
|
6
|
+
|
7
|
+
it "should work with arity 1" do
|
8
|
+
r = Erlang::Node.fun("spec","spec_server","power", 10)
|
9
|
+
r.should eql([:ok, 100])
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should sum" do
|
13
|
+
r = Erlang::Node.fun("spec","spec_server","add", 10, 10)
|
14
|
+
r.should eql([:ok, 20])
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should work the old way" do
|
18
|
+
r = Erlang::Node.rpc("spec","spec_server","add", [10, 10])
|
19
|
+
r.should eql([:ok, 20])
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should have a nice matcher" do
|
23
|
+
r = Erlang::Node.fun("spec","spec_server","add", 10, 20)
|
24
|
+
r.should be_ok
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should have a nicer matcher" do
|
28
|
+
r = Erlang::Node.fun("spec","spec_server","add", 10, 20)
|
29
|
+
r.should be_ok_with(30)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "Numbers" do
|
34
|
+
|
35
|
+
it "should work small ints" do
|
36
|
+
r = Erlang::Node.fun("spec","spec_server", "echo", 99)
|
37
|
+
r.should eql([:ok, 99])
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should work with negative ints" do
|
41
|
+
r = Erlang::Node.fun("spec","spec_server", "echo", -99)
|
42
|
+
r.should eql([:ok, -99])
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should work small ints 8bits" do
|
46
|
+
r = Erlang::Node.fun("spec","spec_server", "echo", 255)
|
47
|
+
r.should eql([:ok, 255])
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should work medium ints +8bits" do
|
51
|
+
pending
|
52
|
+
r = Erlang::Node.fun("spec","spec_server", "echo", 255)
|
53
|
+
r.should eql([:ok, 256])
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should work with floats" do
|
57
|
+
r = Erlang::Node.fun("spec","spec_server", "echo", 0.005)
|
58
|
+
r.should eql([:ok, 0.005])
|
59
|
+
r[1].should be_instance_of(Float)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should work with negative floats" do
|
63
|
+
r = Erlang::Node.fun("spec","spec_server", "echo", -0.5)
|
64
|
+
r.should eql([:ok, -0.5])
|
65
|
+
r[1].should be_instance_of(Float)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should work with floats" do
|
69
|
+
r = Erlang::Node.fun("spec","spec_server", "echo", 1234.5)
|
70
|
+
r.should eql([:ok, 1234.5])
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should work with floats" do
|
74
|
+
r = Erlang::Node.fun("spec","spec_server", "echo", 1234.59999)
|
75
|
+
r.should eql([:ok, 1234.59999])
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "Strings" do
|
81
|
+
|
82
|
+
it "should work with strings" do
|
83
|
+
r = Erlang::Node.fun("spec","spec_server", "dump", ["hi from ruby"])
|
84
|
+
r.should eql([:ok, :ok])
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should work with strings" do
|
88
|
+
r = Erlang::Node.fun("spec","spec_server", "echo", "ping")
|
89
|
+
r.should eql([:ok, "ping"])
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should work with strange chars I" do
|
93
|
+
r = Erlang::Node.fun("spec","spec_server", "echo", "($)[&]{@}")
|
94
|
+
r.should be_ok_with("($)[&]{@}")
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
describe "Collections" do
|
101
|
+
|
102
|
+
it "should work with atoms {sqr, 10}" do
|
103
|
+
r = Erlang::Node.rpc("spec","spec_server","area", {:sqr => 10})
|
104
|
+
r.should be_ok_with(100)
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should work with atoms and integers {circ, 10}" do
|
108
|
+
r = Erlang::Node.rpc("spec","spec_server","area", {:circ => 10})
|
109
|
+
r.should be_ok_with(314.159)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should work with atoms and floats {circ, 10.1}" do
|
113
|
+
r = Erlang::Node.rpc("spec","spec_server","area", {:circ => 10.1})
|
114
|
+
r.should be_ok_with(320.47359589999996)
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should work with atoms {sqr, 10}" do
|
118
|
+
r = Erlang::Node.rpc("spec","spec_server","echo", {:sqr => 10})
|
119
|
+
r.should eql([:ok, [:sqr, 10]])
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should work with arrays" do
|
123
|
+
r = Erlang::Node.fun("spec","spec_server","distance", [1,1], [2,2])
|
124
|
+
r.should be_ok_with(1.4142135623730951)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should return errors" do
|
128
|
+
r = Erlang::Node.fun("p","spec_server","add", 10, 20)
|
129
|
+
r.should_not be_ok
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
require 'rinterface'
|
4
|
+
require 'spec'
|
5
|
+
require 'spec/autorun'
|
6
|
+
module RinterfaceMatchers
|
7
|
+
class BeOk
|
8
|
+
|
9
|
+
def initialize(expect=nil)
|
10
|
+
@expect = expect
|
11
|
+
end
|
12
|
+
|
13
|
+
def matches?(actual)
|
14
|
+
code, @actual = actual
|
15
|
+
cc = code == :ok
|
16
|
+
@expect ? (@actual == @expect && cc) : cc
|
17
|
+
end
|
18
|
+
|
19
|
+
def failure_message; "expected #{@expect} but received #{@actual.inspect}"; end
|
20
|
+
def negative_failure_message; "expected something else then '#{@expect}' but got '#{@actual}'"; end
|
21
|
+
end
|
22
|
+
|
23
|
+
def be_ok; BeOk.new; end
|
24
|
+
|
25
|
+
def be_ok_with(v)
|
26
|
+
BeOk.new(v)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
# Hack-ish
|
32
|
+
module Daemon
|
33
|
+
NAME = "spec_server"
|
34
|
+
COMM = "erl -noshell -W -pa spec -sname spec -s #{NAME}&"
|
35
|
+
|
36
|
+
def self.start!
|
37
|
+
if is_running?
|
38
|
+
puts "Spec daemon running. (PID #{@pid})"
|
39
|
+
else
|
40
|
+
puts "Starting daemon.."
|
41
|
+
system("rake spec:run")#erl -noshell -W -pa spec -sname spec -s spec_server&")
|
42
|
+
sleep 1
|
43
|
+
start!
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.is_running?
|
48
|
+
# Avoids grep from greping itself
|
49
|
+
n2, n1 = NAME[-1].chr, NAME.chop
|
50
|
+
if pid = `ps x | grep #{n1}[#{n2}]`.split(/\s/)[1]
|
51
|
+
@pid = pid.to_i
|
52
|
+
else
|
53
|
+
false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
puts Daemon.start!
|
59
|
+
|
60
|
+
Spec::Runner.configure do |config|
|
61
|
+
config.include RinterfaceMatchers
|
62
|
+
end
|
63
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
-module(spec_server).
|
2
|
+
-export([start/0, power/1, echo/1, dump/1, add/2, area/1, distance/2]).
|
3
|
+
|
4
|
+
start() -> ok.
|
5
|
+
|
6
|
+
echo(R) -> R.
|
7
|
+
|
8
|
+
add(X, Y) -> X + Y.
|
9
|
+
|
10
|
+
power(P) -> P * P.
|
11
|
+
|
12
|
+
dump(W) -> io:format("Received ~p~n", [W]).
|
13
|
+
|
14
|
+
area({sqr, S}) -> S * S;
|
15
|
+
area({ret, W, H}) -> W * H;
|
16
|
+
area({circ, R}) ->
|
17
|
+
io:format("Area received: ~p~n", [R]),
|
18
|
+
Result = 3.14159 * R * R,
|
19
|
+
io:format("Result ~p~n", [Result]),
|
20
|
+
Result.
|
21
|
+
|
22
|
+
distance(P1, P2) ->
|
23
|
+
io:format("Distance received: ~p, ~p~n", [P1, P2]),
|
24
|
+
{X1, Y1} = P1,
|
25
|
+
{X2, Y2} = P2,
|
26
|
+
Result = math:sqrt(math:pow(X2 - X1, 2) + math:pow(Y2 - Y1, 2)),
|
27
|
+
io:format("Result: ~p~n", [Result]),
|
28
|
+
Result.
|
29
|
+
|