schleyfox-ernie 2.2.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/examples/ext.rb ADDED
@@ -0,0 +1,39 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require 'ernie'
3
+
4
+ module Ext
5
+ # Add two numbers together
6
+ def add(a, b)
7
+ a + b
8
+ end
9
+
10
+ def fib(n)
11
+ if n == 0 || n == 1
12
+ 1
13
+ else
14
+ fib(n - 1) + fib(n - 2)
15
+ end
16
+ end
17
+
18
+ def shadow(x)
19
+ "ruby"
20
+ end
21
+
22
+ # Return the given number of bytes
23
+ def bytes(n)
24
+ 'x' * n
25
+ end
26
+
27
+ # Sleep for +sec+ and then return :ok
28
+ def slow(sec)
29
+ sleep(sec)
30
+ :ok
31
+ end
32
+
33
+ # Throw an error
34
+ def error
35
+ raise "abandon hope!"
36
+ end
37
+ end
38
+
39
+ Ernie.expose(:ext, Ext)
data/examples/nat.erl ADDED
@@ -0,0 +1,12 @@
1
+ -module(nat).
2
+ -export([add/2, fib/1, die/1]).
3
+
4
+ add(A, B) ->
5
+ A + B.
6
+
7
+ fib(0) -> 1;
8
+ fib(1) -> 1;
9
+ fib(N) when N > 1 -> fib(N - 1) + fib(N - 2).
10
+
11
+ die(X) ->
12
+ X = 10.
data/ext/Makefile ADDED
@@ -0,0 +1,2 @@
1
+ install:
2
+ erlc -o ../ebin ../elib/*.erl
data/ext/extconf.rb ADDED
@@ -0,0 +1 @@
1
+ # does nothing, Makefile is handwritten
data/lib/ernie.rb ADDED
@@ -0,0 +1,225 @@
1
+ require 'rubygems'
2
+ require 'bert'
3
+ require 'logger'
4
+
5
+ class Ernie
6
+ class << self
7
+ attr_accessor :mods, :current_mod, :log
8
+ attr_accessor :auto_start
9
+ attr_accessor :count, :virgin_procline
10
+ end
11
+
12
+ self.count = 0
13
+ self.virgin_procline = $0
14
+ self.mods = {}
15
+ self.current_mod = nil
16
+ self.log = Logger.new(STDOUT)
17
+ self.log.level = Logger::FATAL
18
+ self.auto_start = true
19
+
20
+ # Record a module.
21
+ # +name+ is the module Symbol
22
+ # +block+ is the Block containing function definitions
23
+ #
24
+ # Returns nothing
25
+ def self.mod(name, block)
26
+ m = Mod.new(name)
27
+ self.current_mod = m
28
+ self.mods[name] = m
29
+ block.call
30
+ end
31
+
32
+ # Record a function.
33
+ # +name+ is the function Symbol
34
+ # +block+ is the Block to associate
35
+ #
36
+ # Returns nothing
37
+ def self.fun(name, block)
38
+ self.current_mod.fun(name, block)
39
+ end
40
+
41
+ # Expose all public methods in a Ruby module:
42
+ # +name+ is the ernie module Symbol
43
+ # +mixin+ is the ruby module whose public methods are exposed
44
+ #
45
+ # Returns nothing
46
+ def self.expose(name, mixin)
47
+ context = Object.new
48
+ context.extend mixin
49
+ mod(name, lambda {
50
+ mixin.public_instance_methods.each do |meth|
51
+ fun(meth.to_sym, context.method(meth))
52
+ end
53
+ })
54
+ context
55
+ end
56
+
57
+ # Set the logfile to given path.
58
+ # +file+ is the String path to the logfile
59
+ #
60
+ # Returns nothing
61
+ def self.logfile(file)
62
+ self.log = Logger.new(file)
63
+ end
64
+
65
+ # Set the log level.
66
+ # +level+ is the Logger level (Logger::WARN, etc)
67
+ #
68
+ # Returns nothing
69
+ def self.loglevel(level)
70
+ self.log.level = level
71
+ end
72
+
73
+ # Dispatch the request to the proper mod:fun.
74
+ # +mod+ is the module Symbol
75
+ # +fun+ is the function Symbol
76
+ # +args+ is the Array of arguments
77
+ #
78
+ # Returns the Ruby object response
79
+ def self.dispatch(mod, fun, args)
80
+ self.mods[mod] || raise(ServerError.new("No such module '#{mod}'"))
81
+ self.mods[mod].funs[fun] || raise(ServerError.new("No such function '#{mod}:#{fun}'"))
82
+ self.mods[mod].funs[fun].call(*args)
83
+ end
84
+
85
+ # Read the length header from the wire.
86
+ # +input+ is the IO from which to read
87
+ #
88
+ # Returns the size Integer if one was read
89
+ # Returns nil otherwise
90
+ def self.read_4(input)
91
+ raw = input.read(4)
92
+ return nil unless raw
93
+ raw.unpack('N').first
94
+ end
95
+
96
+ # Read a BERP from the wire and decode it to a Ruby object.
97
+ # +input+ is the IO from which to read
98
+ #
99
+ # Returns a Ruby object if one could be read
100
+ # Returns nil otherwise
101
+ def self.read_berp(input)
102
+ packet_size = self.read_4(input)
103
+ return nil unless packet_size
104
+ bert = input.read(packet_size)
105
+ BERT.decode(bert)
106
+ end
107
+
108
+ # Write the given Ruby object to the wire as a BERP.
109
+ # +output+ is the IO on which to write
110
+ # +ruby+ is the Ruby object to encode
111
+ #
112
+ # Returns nothing
113
+ def self.write_berp(output, ruby)
114
+ data = BERT.encode(ruby)
115
+ output.write([data.length].pack("N"))
116
+ output.write(data)
117
+ end
118
+
119
+ # Start the processing loop.
120
+ #
121
+ # Loops forever
122
+ def self.start
123
+ self.procline('starting')
124
+ self.log.info("(#{Process.pid}) Starting")
125
+ self.log.debug(self.mods.inspect)
126
+
127
+ input = IO.new(3)
128
+ output = IO.new(4)
129
+ input.sync = true
130
+ output.sync = true
131
+
132
+ loop do
133
+ self.procline('waiting')
134
+ iruby = self.read_berp(input)
135
+ self.count += 1
136
+
137
+ unless iruby
138
+ puts "Could not read BERP length header. Ernie server may have gone away. Exiting now."
139
+ self.log.info("(#{Process.pid}) Could not read BERP length header. Ernie server may have gone away. Exiting now.")
140
+ exit!
141
+ end
142
+
143
+ if iruby.size == 4 && iruby[0] == :call
144
+ mod, fun, args = iruby[1..3]
145
+ self.procline("#{mod}:#{fun}(#{args})")
146
+ self.log.info("-> " + iruby.inspect)
147
+ begin
148
+ res = self.dispatch(mod, fun, args)
149
+ oruby = t[:reply, res]
150
+ self.log.debug("<- " + oruby.inspect)
151
+ write_berp(output, oruby)
152
+ rescue ServerError => e
153
+ oruby = t[:error, t[:server, 0, e.class.to_s, e.message, e.backtrace]]
154
+ self.log.error("<- " + oruby.inspect)
155
+ self.log.error(e.backtrace.join("\n"))
156
+ write_berp(output, oruby)
157
+ rescue Object => e
158
+ oruby = t[:error, t[:user, 0, e.class.to_s, e.message, e.backtrace]]
159
+ self.log.error("<- " + oruby.inspect)
160
+ self.log.error(e.backtrace.join("\n"))
161
+ write_berp(output, oruby)
162
+ end
163
+ elsif iruby.size == 4 && iruby[0] == :cast
164
+ mod, fun, args = iruby[1..3]
165
+ self.procline("#{mod}:#{fun}(#{args})")
166
+ self.log.info("-> " + [:cast, mod, fun, args].inspect)
167
+ begin
168
+ self.dispatch(mod, fun, args)
169
+ rescue Object => e
170
+ # ignore
171
+ end
172
+ write_berp(output, t[:noreply])
173
+ else
174
+ self.procline("invalid request")
175
+ self.log.error("-> " + iruby.inspect)
176
+ oruby = t[:error, t[:server, 0, "Invalid request: #{iruby.inspect}"]]
177
+ self.log.error("<- " + oruby.inspect)
178
+ write_berp(output, oruby)
179
+ end
180
+ end
181
+ end
182
+
183
+ def self.procline(msg)
184
+ $0 = "ernie handler #{VERSION} (ruby) - #{self.virgin_procline} - [#{self.count}] #{msg}"[0..159]
185
+ end
186
+
187
+ def self.version
188
+ yml = YAML.load(File.read(File.join(File.dirname(__FILE__), *%w[.. VERSION.yml])))
189
+ "#{yml[:major]}.#{yml[:minor]}.#{yml[:patch]}"
190
+ rescue
191
+ 'unknown'
192
+ end
193
+
194
+ VERSION = self.version
195
+ end
196
+
197
+ class Ernie::ServerError < StandardError; end
198
+
199
+ class Ernie::Mod
200
+ attr_accessor :name, :funs
201
+
202
+ def initialize(name)
203
+ self.name = name
204
+ self.funs = {}
205
+ end
206
+
207
+ def fun(name, block)
208
+ raise TypeError, "block required" if block.nil?
209
+ self.funs[name] = block
210
+ end
211
+ end
212
+
213
+ # Root level calls
214
+
215
+ def logfile(name)
216
+ Ernie.logfile(name)
217
+ end
218
+
219
+ def loglevel(level)
220
+ Ernie.loglevel(level)
221
+ end
222
+
223
+ at_exit do
224
+ Ernie.start if Ernie.auto_start
225
+ end
@@ -0,0 +1,77 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ PORT = 27118
4
+
5
+ class ErnieServerTest < Test::Unit::TestCase
6
+ context "An Ernie Server" do
7
+ setup do
8
+ `#{ERNIE_ROOT}/bin/ernie -c #{ERNIE_ROOT}/test/sample/sample.cfg \
9
+ -P /tmp/ernie.pid \
10
+ -p #{PORT} \
11
+ -d`
12
+ @svc = BERTRPC::Service.new('localhost', PORT)
13
+ loop do
14
+ begin
15
+ @svc.call.ext.zeronary
16
+ break
17
+ rescue Object => e
18
+ sleep 0.1
19
+ end
20
+ end
21
+ end
22
+
23
+ context "call" do
24
+ should "handle zeronary" do
25
+ assert_equal :foo, @svc.call.ext.zeronary
26
+ end
27
+
28
+ should "handle unary" do
29
+ assert_equal 5, @svc.call.ext.unary(5)
30
+ end
31
+
32
+ should "handle binary" do
33
+ assert_equal 7, @svc.call.ext.binary(5, 2)
34
+ end
35
+
36
+ should "handle ternary" do
37
+ assert_equal 10, @svc.call.ext.ternary(5, 2, 3)
38
+ end
39
+
40
+ should "handle massive binaries" do
41
+ assert_equal 8 * 1024 * 1024, @svc.call.ext.big(8 * 1024 * 1024).size
42
+ end
43
+
44
+ should "get an error on missing module" do
45
+ begin
46
+ @svc.call.failboat.mcfail(:fail)
47
+ fail "Expected a BERTRPC::ServerError"
48
+ rescue BERTRPC::ServerError => e
49
+ assert_equal "No such module 'failboat'", e.message
50
+ end
51
+ end
52
+
53
+ should "get an error on missing function" do
54
+ begin
55
+ @svc.call.ext.mcfail(:fail)
56
+ fail "Expected a BERTRPC::ServerError"
57
+ rescue BERTRPC::ServerError => e
58
+ assert_equal "No such function 'ext:mcfail'", e.message
59
+ end
60
+ end
61
+ end
62
+
63
+ context "cast" do
64
+ should "be received and return immediately" do
65
+ t0 = Time.now
66
+ assert_equal nil, @svc.cast.ext.set_state(7)
67
+ assert Time.now - t0 < 1
68
+ assert_equal 7, @svc.call.ext.get_state
69
+ end
70
+ end
71
+
72
+ teardown do
73
+ pid = File.read('/tmp/ernie.pid')
74
+ `kill -9 #{pid}`
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,43 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class ErnieTest < Test::Unit::TestCase
4
+ module TestExposingModule
5
+ def foo
6
+ end
7
+
8
+ def bar(a, b, c=nil)
9
+ [a, b, c]
10
+ end
11
+
12
+ protected
13
+ def baz
14
+ end
15
+
16
+ private
17
+ def bling
18
+ end
19
+ end
20
+
21
+ context "expose" do
22
+ setup { Ernie.expose :expo, TestExposingModule }
23
+ teardown { Ernie.mods.clear }
24
+
25
+ should "add all public methods from the module" do
26
+ assert_not_nil Ernie.mods[:expo].funs[:foo]
27
+ assert_not_nil Ernie.mods[:expo].funs[:bar]
28
+ end
29
+
30
+ should "not expose protected methods" do
31
+ assert_nil Ernie.mods[:expo].funs[:baz]
32
+ end
33
+
34
+ should "not expose private methods" do
35
+ assert_nil Ernie.mods[:expo].funs[:bling]
36
+ end
37
+
38
+ should "dispatch to module methods properly" do
39
+ res = Ernie.dispatch(:expo, :bar, ['a', :b, { :fiz => 42 }])
40
+ assert_equal ['a', :b, { :fiz => 42 }], res
41
+ end
42
+ end
43
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,17 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ ERNIE_ROOT = File.join(File.dirname(__FILE__), *%w[..])
6
+
7
+ $:.unshift(File.join(ERNIE_ROOT, 'lib'))
8
+
9
+ require 'ernie'
10
+ Ernie.auto_start = false
11
+
12
+ begin
13
+ require 'bertrpc'
14
+ rescue LoadError
15
+ puts "You need bertrpc gem installed to run tests."
16
+ exit!(1)
17
+ end
data/test/load.rb ADDED
@@ -0,0 +1,18 @@
1
+ require 'bertrpc'
2
+
3
+ $stdout.sync = true
4
+
5
+ threads = []
6
+ svc = BERTRPC::Service.new('localhost', 8000)
7
+
8
+ 8.times do
9
+ threads << Thread.new do
10
+ i = 0
11
+ 10.times { i += svc.call.calc.add(1, 2); print '.'; $stdout.flush }
12
+ print "(#{i})"
13
+ end
14
+ end
15
+
16
+ threads.each { |t| t.join }
17
+
18
+ puts
@@ -0,0 +1,42 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', '..', 'lib'))
2
+ require 'ernie'
3
+
4
+ module Ext
5
+ @@state = 0
6
+
7
+ def zeronary
8
+ :foo
9
+ end
10
+
11
+ def unary(a)
12
+ a
13
+ end
14
+
15
+ def binary(a, b)
16
+ a + b
17
+ end
18
+
19
+ def ternary(a, b, c)
20
+ a + b + c
21
+ end
22
+
23
+ def set_state(x)
24
+ @@state = x
25
+ sleep 5
26
+ nil
27
+ end
28
+
29
+ def get_state
30
+ @@state
31
+ end
32
+
33
+ def big(x)
34
+ 'a' * x
35
+ end
36
+
37
+ def cry
38
+ raise "abandon hope!"
39
+ end
40
+ end
41
+
42
+ Ernie.expose(:ext, Ext)
@@ -0,0 +1,4 @@
1
+ [{module, ext},
2
+ {type, external},
3
+ {command, "ruby /Users/tom/dev/mojombo/ernie/test/sample/ext.rb"},
4
+ {count, 1}].
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: schleyfox-ernie
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 2
7
+ - 2
8
+ - 1
9
+ version: 2.2.1
10
+ platform: ruby
11
+ authors:
12
+ - Tom Preston-Werner
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-03-12 00:00:00 -05:00
18
+ default_executable: ernie
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: bert
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 1
29
+ - 1
30
+ - 0
31
+ version: 1.1.0
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: bertrpc
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 1
43
+ - 0
44
+ - 0
45
+ version: 1.0.0
46
+ type: :runtime
47
+ version_requirements: *id002
48
+ description: Ernie is an Erlang/Ruby hybrid BERT-RPC server implementation packaged as a gem.
49
+ email: tom@mojombo.com
50
+ executables:
51
+ - ernie
52
+ extensions:
53
+ - ext/extconf.rb
54
+ - ext/extconf.rb
55
+ extra_rdoc_files:
56
+ - LICENSE
57
+ - README.md
58
+ files:
59
+ - .document
60
+ - .gitignore
61
+ - History.txt
62
+ - LICENSE
63
+ - README.md
64
+ - Rakefile
65
+ - VERSION.yml
66
+ - bin/ernie
67
+ - contrib/ebench.erl
68
+ - ebin/ernie_server_app.app
69
+ - elib/asset_pool.erl
70
+ - elib/asset_pool_sup.erl
71
+ - elib/bert.erl
72
+ - elib/ernie.hrl
73
+ - elib/ernie_access_logger.erl
74
+ - elib/ernie_access_logger_sup.erl
75
+ - elib/ernie_admin.erl
76
+ - elib/ernie_config.erl
77
+ - elib/ernie_native.erl
78
+ - elib/ernie_server.erl
79
+ - elib/ernie_server_app.erl
80
+ - elib/ernie_server_sup.erl
81
+ - elib/logger.erl
82
+ - elib/logger_sup.erl
83
+ - elib/port_wrapper.erl
84
+ - ernie.gemspec
85
+ - examples/example.cfg
86
+ - examples/ext.erl
87
+ - examples/ext.rb
88
+ - examples/nat.erl
89
+ - ext/Makefile
90
+ - ext/extconf.rb
91
+ - lib/ernie.rb
92
+ - test/ernie_server_test.rb
93
+ - test/ernie_test.rb
94
+ - test/helper.rb
95
+ - test/load.rb
96
+ - test/sample/ext.rb
97
+ - test/sample/sample.cfg
98
+ has_rdoc: true
99
+ homepage: http://github.com/mojombo/ernie
100
+ licenses: []
101
+
102
+ post_install_message:
103
+ rdoc_options:
104
+ - --charset=UTF-8
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ segments:
112
+ - 0
113
+ version: "0"
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ segments:
119
+ - 0
120
+ version: "0"
121
+ requirements: []
122
+
123
+ rubyforge_project: ernie
124
+ rubygems_version: 1.3.6
125
+ signing_key:
126
+ specification_version: 3
127
+ summary: Ernie is a BERT-RPC server implementation.
128
+ test_files:
129
+ - test/ernie_server_test.rb
130
+ - test/ernie_test.rb
131
+ - test/helper.rb
132
+ - test/load.rb
133
+ - test/sample/ext.rb
134
+ - examples/ext.rb