oflow 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c60c057bc56ccdf1457715ab9e7c73af54614bb2
4
- data.tar.gz: b20b4b16b01f875c7848bb9f442ca3e67213cda7
3
+ metadata.gz: d36c10496898f7cb6df17b4be3ba103cb8098169
4
+ data.tar.gz: 563283b71b4e11bd6b207a464b9204722d472f02
5
5
  SHA512:
6
- metadata.gz: a22ef32835f66cb4cccc81330a76d82c25a03ce3c646a878ea9ebe4b89fc4b2011747e540a53fe1e87c7e530e34407af8309139e5a87bbec1cd124edc3157930
7
- data.tar.gz: d9e903de2ecafa7b7cb386182bcc194905f7d571801dccde61ebbab82e4236f4b28626854d61d8f2556164d8e710536d120e5a1ec103a0edf10d9752af2d52bb
6
+ metadata.gz: 0d0572af84b50544594563dd86e625c1b86b444702d793ea1585cc870b31f07194903677e1a65a9c6b0d798b7e7a44031c233ca8e3764f36273c20fdb13ce9ff
7
+ data.tar.gz: 6ca630368b0c25ee789e761043bbca9dcfb8e11fadf590ef8b19017b0d5ea1811c36135d9c9f2e0ab41b97e8f8776a7d969a88678c5c5a223eea4e2adf203c3c
data/README.md CHANGED
@@ -25,6 +25,10 @@ Follow [@peterohler on Twitter](http://twitter.com/#!/peterohler) for announceme
25
25
 
26
26
  ## Release Notes
27
27
 
28
+ ### Release 0.6
29
+
30
+ - Added HTTP Server Actor that acts as a simple HTTP server.
31
+
28
32
  ### Release 0.5
29
33
 
30
34
  - Added Persister Actor that acts as a simple local store database.
@@ -119,13 +123,13 @@ Hello World!
119
123
 
120
124
  ## Future Features
121
125
 
122
- - Balancer Actor that distributes to a set of others Tasks based on how busy each is.
126
+ - HTTP Server Actor
123
127
 
124
- - Merger Actor that waits for a criteria to be met before continuing.
128
+ - OmniGraffle file input for configuration.
125
129
 
126
- - HTTP Server Actor
130
+ - .svg file input for configuration.
127
131
 
128
- - Persister Actor that writes to disk and reads on start)
132
+ - Visio file input for configuration.
129
133
 
130
134
  - CallOut Actor that uses pipes and fork to use a non-Ruby actor.
131
135
 
@@ -133,12 +137,6 @@ Hello World!
133
137
 
134
138
  - Dynamic links to Tasks and Flows.
135
139
 
136
- - OmniGraffle file input for configuration.
137
-
138
- - .svg file input for configuration.
139
-
140
- - Visio file input for configuration.
141
-
142
140
  - High performance C version. Proof of concept puts the performance range at
143
141
  around 10M operations per second where an operation is one task execution per
144
142
  thread.
@@ -10,6 +10,8 @@ require 'oflow/actors/log'
10
10
  require 'oflow/actors/errorhandler'
11
11
 
12
12
  require 'oflow/actors/balancer'
13
+ require 'oflow/actors/trigger'
13
14
  require 'oflow/actors/merger'
14
15
  require 'oflow/actors/persister'
15
16
  require 'oflow/actors/timer'
17
+ require 'oflow/actors/httpserver'
@@ -0,0 +1,238 @@
1
+
2
+ require 'socket'
3
+ require 'time'
4
+ require 'fcntl'
5
+
6
+ module OFlow
7
+ module Actors
8
+
9
+ # Provides a simple HTTP server that accepts requests which then trigger a
10
+ # flow. The execution flow should end back at the server so it can sent a
11
+ # response to the requester.
12
+ class HttpServer < Trigger
13
+
14
+ def initialize(task, options)
15
+ super
16
+ @sessions = { }
17
+
18
+ @server = TCPServer.new(@port)
19
+ @server.fcntl(Fcntl::F_SETFL, @server.fcntl(Fcntl::F_GETFL, 0) | Fcntl::O_NONBLOCK)
20
+ @server_loop = Thread.start(self) do |me|
21
+ Thread.current[:name] = me.task.full_name() + '-server'
22
+ while Task::CLOSING != task.state
23
+ begin
24
+ if Task::BLOCKED == task.state || Task::STOPPED == task.state
25
+ sleep(0.1)
26
+ next
27
+ end
28
+ session = @server.accept_nonblock()
29
+ session.fcntl(Fcntl::F_SETFL, session.fcntl(Fcntl::F_GETFL, 0) | Fcntl::O_NONBLOCK)
30
+ @count += 1
31
+ req = read_req(session, @count)
32
+ @sessions[@count] = session
33
+ resp = {
34
+ status: 200,
35
+ body: nil,
36
+ headers: {
37
+ 'Content-Type' => 'text/html',
38
+ }
39
+ }
40
+ box = new_event()
41
+ box.contents[:request] = req
42
+ box.contents[:response] = resp
43
+ task.links.each_key do |key|
44
+ continue if :success == key || 'success' == key
45
+ begin
46
+ task.ship(key, box)
47
+ rescue BlockedError => e
48
+ task.warn("Failed to ship timer #{box.contents} to #{key}. Task blocked.")
49
+ rescue BusyError => e
50
+ task.warn("Failed to ship timer #{box.contents} to #{key}. Task busy.")
51
+ end
52
+ end
53
+ rescue IO::WaitReadable, Errno::EINTR
54
+ IO.select([@server], nil, nil, 0.5)
55
+ rescue Exception => e
56
+ task.handle_error(e)
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ def perform(op, box)
63
+ case op
64
+ when :reply
65
+ req_id = box.get(@req_id_path)
66
+ if (session = @sessions[req_id]).nil?
67
+ raise NotFoundError.new(task.full_name, 'session', req_id)
68
+ end
69
+ if (resp = box.get(@response_path)).nil?
70
+ raise NotFoundError.new(task.full_name, 'response', @response_path)
71
+ end
72
+ body = resp[:body]
73
+ body = '' if body.nil?
74
+ status = resp[:status]
75
+ headers = ["HTTP/1.1 #{status} {STATUS_MESSAGES[status]}"]
76
+ resp[:headers].each do |k,v|
77
+ headers << "#{k}: #{v}"
78
+ end
79
+ headers << "Content-Length: #{body.length}\r\n\r\n"
80
+ session.puts headers.join("\r\n")
81
+ session.puts body
82
+ session.close
83
+ @sessions.delete(req_id)
84
+ when :skip
85
+ req_id = box.get(@req_id_path)
86
+ if (session = @sessions[req_id]).nil?
87
+ raise NotFoundError.new(task.full_name, 'session', req_id)
88
+ end
89
+ @sessions.delete(req_id)
90
+ else
91
+ raise OpError.new(task.full_name, op)
92
+ end
93
+ task.ship(:success, Box.new(nil, box.tracker)) unless task.links[:success].nil?
94
+ end
95
+
96
+ def set_options(options)
97
+ super
98
+ @port = options.fetch(:port, 6060)
99
+ @req_id_path = options.fetch(:req_id_path, 'request:id')
100
+ @response_path = options.fetch(:response_path, 'response')
101
+ @read_timeout = options.fetch(:read_timeout, 1.0)
102
+ end
103
+
104
+ def read_req(session, id)
105
+ req = {
106
+ id: id,
107
+ }
108
+ line = session.gets()
109
+ parts = line.split(' ')
110
+ req[:method] = parts[0]
111
+ req[:protocol] = parts[2]
112
+ path, arg_str = parts[1].split('?', 2)
113
+ req[:path] = path
114
+ args = nil
115
+ unless arg_str.nil?
116
+ args = arg_str.split('&').map { |pair| pair.split('=') }
117
+ end
118
+ req[:args] = args
119
+
120
+ # Read the rest of the lines and the body if there is one.
121
+ len = 0
122
+ while line = session.gets()
123
+ line.strip!
124
+ break if 0 == line.size
125
+ parts = line.split(':', 2)
126
+ next unless 2 == parts.size
127
+ key = parts[0]
128
+ value = parts[1].strip()
129
+ if 'Content-Length' == key
130
+ value = value.to_i
131
+ len = value
132
+ end
133
+ req[key] = value
134
+ end
135
+ req[:body] = read_timeout(session, len, @read_timeout) if 0 < len
136
+ req
137
+ end
138
+
139
+ def read_timeout(session, len, timeout)
140
+ str = ''
141
+ done = Time.now() + timeout
142
+ loop do
143
+ begin
144
+ str = session.readpartial(len, str)
145
+ break if str.size == len
146
+ rescue Errno::EAGAIN => e
147
+ raise e if IO.select([session], nil, nil, done - Time.now()).nil?
148
+ retry
149
+ end
150
+ end
151
+ str
152
+ end
153
+
154
+ STATUS_MESSAGES = {
155
+ 100 => 'Continue',
156
+ 101 => 'Switching Protocols',
157
+ 102 => 'Processing',
158
+ 200 => 'OK',
159
+ 201 => 'Created',
160
+ 202 => 'Accepted',
161
+ 203 => 'Non-Authoritative Information',
162
+ 204 => 'No Content',
163
+ 205 => 'Reset Content',
164
+ 206 => 'Partial Content',
165
+ 207 => 'Multi-Status',
166
+ 208 => 'Already Reported',
167
+ 226 => 'IM Used',
168
+ 300 => 'Multiple Choices',
169
+ 301 => 'Moved Permanently',
170
+ 302 => 'Found',
171
+ 303 => 'See Other',
172
+ 304 => 'Not Modified',
173
+ 305 => 'Use Proxy',
174
+ 306 => 'Switch Proxy',
175
+ 307 => 'Temporary Redirect',
176
+ 308 => 'Permanent Redirect',
177
+ 400 => 'Bad Request',
178
+ 401 => 'Unauthorized',
179
+ 402 => 'Payment Required',
180
+ 403 => 'Forbidden',
181
+ 404 => 'Not Found',
182
+ 405 => 'Method Not Allowed',
183
+ 406 => 'Not Acceptable',
184
+ 407 => 'Proxy Authentication Required',
185
+ 408 => 'Request Timeout',
186
+ 409 => 'Conflict',
187
+ 410 => 'Gone',
188
+ 411 => 'Length Required',
189
+ 412 => 'Precondition Failed',
190
+ 413 => 'Request Entity Too Large',
191
+ 414 => 'Request-URI Too Long',
192
+ 415 => 'Unsupported Media Type',
193
+ 416 => 'Requested Range Not Satisfiable',
194
+ 417 => 'Expectation Failed',
195
+ 418 => "I'm a teapot",
196
+ 419 => 'Authentication Timeout',
197
+ 420 => 'Method Failure',
198
+ 422 => 'Unprocessed Entity',
199
+ 423 => 'Locked',
200
+ 424 => 'Failed Dependency',
201
+ 425 => 'Unordered Collection',
202
+ 426 => 'Upgrade Required',
203
+ 428 => 'Precondition Required',
204
+ 429 => 'Too Many Requests',
205
+ 431 => 'Request Header Fields Too Long',
206
+ 440 => 'Login Timeout',
207
+ 444 => 'No Response',
208
+ 449 => 'Retry With',
209
+ 450 => 'Blocked by Windows Parental Controls',
210
+ 451 => 'Unavailable For Legal Reasons',
211
+ 494 => 'Request Header Too Large',
212
+ 495 => 'Cert Error',
213
+ 496 => 'No Cert',
214
+ 497 => 'HTTP tp HTTP',
215
+ 499 => 'Client Closed Request',
216
+ 500 => 'Internal Server Error',
217
+ 501 => 'Not Implemented',
218
+ 502 => 'Bad Gateway',
219
+ 503 => 'Service Unavailable',
220
+ 504 => 'Gateway Timeout',
221
+ 505 => 'HTTP Version Not Supported',
222
+ 506 => 'Variant Also Negotiates',
223
+ 507 => 'Insufficient Storage',
224
+ 508 => 'Loop Detected',
225
+ 509 => 'Bandwidth Limit Exceeded',
226
+ 510 => 'Not Extended',
227
+ 511 => 'Network Authentication Required',
228
+ 520 => 'Original Error',
229
+ 522 => 'Connection timed out',
230
+ 523 => 'Proxy Declined Request',
231
+ 524 => 'A timeout occurred',
232
+ 598 => 'Network read timeout error',
233
+ 599 => 'Network connect timeout error',
234
+ }
235
+
236
+ end # HttpServer
237
+ end # Actors
238
+ end # OFlow
@@ -14,6 +14,11 @@ module OFlow
14
14
  :warn => Logger::Severity::WARN,
15
15
  :info => Logger::Severity::INFO,
16
16
  :debug => Logger::Severity::DEBUG,
17
+ :FATAL => Logger::Severity::FATAL,
18
+ :ERROR => Logger::Severity::ERROR,
19
+ :WARN => Logger::Severity::WARN,
20
+ :INFO => Logger::Severity::INFO,
21
+ :DEBUG => Logger::Severity::DEBUG,
17
22
  }
18
23
  def initialize(task, options={})
19
24
  @logger = nil
@@ -0,0 +1,46 @@
1
+
2
+ module OFlow
3
+ module Actors
4
+
5
+ class Trigger < Actor
6
+
7
+ # Label for the Tracker is used and for trigger content.
8
+ attr_reader :label
9
+ # Boolean flag indicating a tracker should be added to the trigger content
10
+ # if true.
11
+ attr_reader :with_tracker
12
+ # The number of time the timer has fired or shipped.
13
+ attr_reader :count
14
+
15
+ def initialize(task, options)
16
+ super
17
+ @count = 0
18
+ set_options(options)
19
+ end
20
+
21
+ def new_event()
22
+ tracker = @with_tracker ? Tracker.create(@label) : nil
23
+ Box.new({ source: task.full_name, label: @label, timestamp: Time.now.utc() }, tracker)
24
+ end
25
+
26
+ def set_options(options)
27
+ set_with_tracker(options[:with_tracker])
28
+ @label = options[:label].to_s
29
+ end
30
+
31
+ def set_label(v)
32
+ v = v.to_s unless v.nil?
33
+ @label = v
34
+ end
35
+
36
+ def set_with_tracker(v)
37
+ v = false if v.nil?
38
+ unless true == v || false == v
39
+ raise ConfigError.new("Expected with_tracker to be a boolean, not a #{v.class}.")
40
+ end
41
+ @with_tracker = v
42
+ end
43
+
44
+ end # Trigger
45
+ end # Actors
46
+ end # OFlow
@@ -74,7 +74,11 @@ module OFlow
74
74
 
75
75
  # Returns a string representation of the Box and contents.
76
76
  def to_s()
77
- "Box{#{@contents}, tracker: #{@tracker}}"
77
+ if @tracker.nil?
78
+ "Box{#{@contents}}"
79
+ else
80
+ "Box{#{@contents}, tracker: #{@tracker}}"
81
+ end
78
82
  end
79
83
  alias inspect to_s
80
84
 
@@ -152,14 +156,14 @@ module OFlow
152
156
  value[ps] = _aset(path[1..-1], value[ps], rv)
153
157
  end
154
158
  when NilClass
155
- begin
156
- i = p.to_i
157
- value = []
158
- value[i] = _aset(path[1..-1], nil, rv)
159
- rescue
159
+ if /^\d+$/.match(p).nil?
160
160
  ps = p.to_sym
161
161
  value = {}
162
162
  value[ps] = _aset(path[1..-1], nil, rv)
163
+ else
164
+ i = p.to_i
165
+ value = []
166
+ value[i] = _aset(path[1..-1], nil, rv)
163
167
  end
164
168
  else
165
169
  raise FrozenError.new(p, value)
@@ -44,6 +44,13 @@ module OFlow
44
44
  end
45
45
  end # LinkError
46
46
 
47
+ # An Exception indicating an item was not found.
48
+ class NotFoundError < Exception
49
+ def initialize(name, type, id)
50
+ super("The #{type} identified as '#{id}' was not found in #{name}.")
51
+ end
52
+ end # NotFoundError
53
+
47
54
  # An Exception raised when there are validation errors.
48
55
  class ValidateError < Exception
49
56
  attr_accessor :problems
@@ -59,6 +59,10 @@ module OFlow
59
59
  def links()
60
60
  @links
61
61
  end
62
-
62
+
63
+ def has_links?()
64
+ !@links.nil? && !@links.empty?
65
+ end
66
+
63
67
  end # HasLinks
64
68
  end # OFlow
@@ -30,7 +30,14 @@ module OFlow
30
30
  # @param msg [String] message to log
31
31
  # @param fn [String] full name of Task or Flow calling the log function
32
32
  def log_msg(level, msg, fn)
33
+ # Abort early if the log severity/level would not log the message. This
34
+ # also allows non-Loggers to be used in place of the Log Actor.
35
+ return if Env.log_level > Actors::Log::SEVERITY_MAP[level]
36
+
33
37
  lt = log()
38
+ # To prevent infinite looping, don't allow the logger to log to itself.
39
+ return if self == lt
40
+
34
41
  unless lt.nil?
35
42
  lt.receive(level, Box.new([msg, fn]))
36
43
  else
@@ -52,6 +52,7 @@ module OFlow
52
52
  init_links()
53
53
  set_options(options)
54
54
 
55
+ info("Creating actor #{actor_class} with options #{options}.")
55
56
  @actor = actor_class.new(self, options)
56
57
  raise Exception.new("#{actor} does not respond to the perform() method.") unless @actor.respond_to?(:perform)
57
58
 
@@ -76,6 +77,7 @@ module OFlow
76
77
 
77
78
  @current_req = req
78
79
  begin
80
+ info("perform(#{req.op}, #{req.box})")
79
81
  @actor.perform(req.op, req.box) unless req.nil?
80
82
  rescue Exception => e
81
83
  handle_error(e)
@@ -279,6 +281,8 @@ module OFlow
279
281
  # @param op [Symbol] operation to perform
280
282
  # @param box [Box] contents or data for the request
281
283
  def receive(op, box)
284
+ info("receive(#{op}, #{box}) #{state_string}")
285
+
282
286
  return if CLOSING == @state
283
287
 
284
288
  raise BlockedError.new() if BLOCKED == @state
@@ -313,6 +317,7 @@ module OFlow
313
317
  box.freeze() unless box.nil?
314
318
  link = resolve_link(dest)
315
319
  raise LinkError.new(dest) if link.nil? || link.target.nil?
320
+ info("shipping #{box} to #{link.target_name}:#{link.op}")
316
321
  link.target.receive(link.op, box)
317
322
  link
318
323
  end
@@ -1,5 +1,5 @@
1
1
 
2
2
  module OFlow
3
3
  # Current version of the module.
4
- VERSION = '0.5.0'
4
+ VERSION = '0.6.0'
5
5
  end
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ [ File.dirname(__FILE__),
5
+ File.join(File.dirname(__FILE__), "../../lib"),
6
+ File.join(File.dirname(__FILE__), "..")
7
+ ].each { |path| $: << path unless $:.include?(path) }
8
+
9
+ require 'net/http'
10
+ require 'test/unit'
11
+ require 'oflow'
12
+ require 'oflow/test'
13
+
14
+ class Reply < ::OFlow::Actor
15
+
16
+ def initialize(task, options)
17
+ super
18
+ end
19
+
20
+ def perform(op, box)
21
+ reply = box.get('request')
22
+ box = box.set('response:body', reply.to_s)
23
+ task.ship(nil, box)
24
+ end
25
+
26
+ end # Reply
27
+
28
+ class HttpServerTest < ::Test::Unit::TestCase
29
+
30
+ def test_httpserver
31
+ ::OFlow::Env.flow('http-server', port: 6060) { |f|
32
+ f.task('server', ::OFlow::Actors::HttpServer) { |t|
33
+ t.link(nil, :reply, nil)
34
+ }
35
+ f.task(:reply, Reply) { |t|
36
+ t.link(nil, :server, :reply)
37
+ }
38
+ }
39
+ # GET
40
+ uri = URI('http://localhost:6060/test?a=1&b=two')
41
+ reply = Net::HTTP.get(uri)
42
+ assert_equal(%|{:id=>1, :method=>"GET", :protocol=>"HTTP/1.1", :path=>"/test", :args=>[["a", "1"], ["b", "two"]], "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "Accept"=>"*/*", "User-Agent"=>"Ruby", "Host"=>"localhost:6060"}|,
43
+ reply, 'expected reply from GET')
44
+
45
+ # POST
46
+ uri = URI('http://localhost:6060/test')
47
+ response = Net::HTTP.post_form(uri, 'a' => '1', 'b' => 'two')
48
+ reply = response.body
49
+
50
+ assert_equal(%|{:id=>2, :method=>\"POST\", :protocol=>\"HTTP/1.1\", :path=>\"/test\", :args=>nil, \"Accept-Encoding\"=>\"gzip;q=1.0,deflate;q=0.6,identity;q=0.3\", \"Accept\"=>\"*/*\", \"User-Agent\"=>\"Ruby\", \"Host\"=>\"localhost:6060\", \"Content-Type\"=>\"application/x-www-form-urlencoded\", \"Content-Length\"=>9, :body=>\"a=1&b=two\"}|,
51
+ reply, 'expected reply from POST')
52
+
53
+ ::OFlow::Env.flush()
54
+ ::OFlow::Env.clear()
55
+ end
56
+
57
+ end # HttpServerTest
@@ -27,3 +27,4 @@ require 'actors/log_test'
27
27
  require 'actors/merger_test'
28
28
  require 'actors/persister_test'
29
29
  require 'actors/timer_test'
30
+ require 'actors/httpserver_test'
@@ -17,7 +17,8 @@ class Noise < ::OFlow::Actor
17
17
  end
18
18
 
19
19
  def perform(op, box)
20
- task.info("op: #{op}, box: #{box.contents}")
20
+ task.warn("op: #{op}, box: #{box.contents}")
21
+ task.ship(nil, ::OFlow::Box.new([box.contents])) if task.has_links?
21
22
  end
22
23
 
23
24
  end # Noise
@@ -28,6 +29,7 @@ class FlowLogTest < ::Test::Unit::TestCase
28
29
  def test_flow_log_relay
29
30
  trigger = nil
30
31
  collector = nil
32
+ ::OFlow::Env.log_level = Logger::WARN
31
33
  ::OFlow::Env.flow('log_relay') { |f|
32
34
  trigger = f.task('noise', Noise)
33
35
  f.task(:log, Collector) { |t|
@@ -48,7 +50,8 @@ class FlowLogTest < ::Test::Unit::TestCase
48
50
  def test_flow_log_var
49
51
  trigger = nil
50
52
  collector = nil
51
- ::OFlow::Env.flow('log_relay') { |f|
53
+ ::OFlow::Env.log_level = Logger::WARN
54
+ ::OFlow::Env.flow('log_var') { |f|
52
55
  trigger = f.task('noise', Noise)
53
56
  f.log = f.task(:collector, Collector) { |t|
54
57
  collector = t.actor
@@ -59,7 +62,7 @@ class FlowLogTest < ::Test::Unit::TestCase
59
62
 
60
63
  assert_equal(collector.collection.size, 1)
61
64
  assert_equal(collector.collection[0][0], 'op: speak, box: 7')
62
- assert_equal(collector.collection[0][1], ':log_relay:noise')
65
+ assert_equal(collector.collection[0][1], ':log_var:noise')
63
66
 
64
67
  ::OFlow::Env.clear()
65
68
  end
@@ -68,7 +71,8 @@ class FlowLogTest < ::Test::Unit::TestCase
68
71
  def test_flow_log_env
69
72
  trigger = nil
70
73
  collector = nil
71
- ::OFlow::Env.flow('log_relay') { |f|
74
+ ::OFlow::Env.log_level = Logger::WARN
75
+ ::OFlow::Env.flow('log_env') { |f|
72
76
  trigger = f.task('noise', Noise)
73
77
  ::OFlow::Env.log = f.task(:collector, Collector) { |t|
74
78
  collector = t.actor
@@ -79,9 +83,40 @@ class FlowLogTest < ::Test::Unit::TestCase
79
83
 
80
84
  assert_equal(collector.collection.size, 1)
81
85
  assert_equal(collector.collection[0][0], 'op: speak, box: 7')
82
- assert_equal(collector.collection[0][1], ':log_relay:noise')
86
+ assert_equal(collector.collection[0][1], ':log_env:noise')
87
+
88
+ ::OFlow::Env.clear()
89
+ end
90
+
91
+ def test_flow_log_info
92
+ trigger = nil
93
+ collector = nil
94
+ ::OFlow::Env.log_level = Logger::WARN
95
+ ::OFlow::Env.flow('log_info') { |f|
96
+ f.log = f.task(:collector, Collector) { |t|
97
+ collector = t.actor
98
+ }
99
+ # Set after log to avoid race condition with the creation of the collector
100
+ # and the assignment to f.log. The race is whether a log message is
101
+ # displayed on the output.
102
+ ::OFlow::Env.log_level = Logger::INFO
103
+ trigger = f.task('noise', Noise) { |t|
104
+ t.link(nil, :collector, nil)
105
+ }
106
+ }
107
+ trigger.receive(:speak, ::OFlow::Box.new(7))
108
+ ::OFlow::Env.flush()
109
+
110
+ entries = collector.collection.map { |entry| entry[0] }
111
+ assert_equal(["Creating actor Noise with options {:state=>1}.",
112
+ "receive(speak, Box{7}) RUNNING",
113
+ "perform(speak, Box{7})",
114
+ "op: speak, box: 7",
115
+ "shipping Box{[7]} to collector:",
116
+ 7], entries)
83
117
 
84
118
  ::OFlow::Env.clear()
119
+ ::OFlow::Env.log_level = Logger::WARN
85
120
  end
86
121
 
87
122
  end # FlowLogTest
@@ -17,7 +17,7 @@ class Hop < ::OFlow::Actor
17
17
  end
18
18
 
19
19
  def perform(op, box)
20
- task.info("#{op} #{box.contents}")
20
+ task.warn("#{op} #{box.contents}")
21
21
  task.ship(op, box)
22
22
  end
23
23
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oflow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Ohler
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-02-16 00:00:00.000000000 Z
11
+ date: 2014-03-13 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Operations Workflow in Ruby. This implements a workflow/process flow
14
14
  using multiple task nodes that each have their own queues and execution thread.
@@ -25,12 +25,14 @@ files:
25
25
  - lib/oflow/actors.rb
26
26
  - lib/oflow/actors/balancer.rb
27
27
  - lib/oflow/actors/errorhandler.rb
28
+ - lib/oflow/actors/httpserver.rb
28
29
  - lib/oflow/actors/ignore.rb
29
30
  - lib/oflow/actors/log.rb
30
31
  - lib/oflow/actors/merger.rb
31
32
  - lib/oflow/actors/persister.rb
32
33
  - lib/oflow/actors/relay.rb
33
34
  - lib/oflow/actors/timer.rb
35
+ - lib/oflow/actors/trigger.rb
34
36
  - lib/oflow/box.rb
35
37
  - lib/oflow/env.rb
36
38
  - lib/oflow/errors.rb
@@ -51,6 +53,7 @@ files:
51
53
  - lib/oflow/tracker.rb
52
54
  - lib/oflow/version.rb
53
55
  - test/actors/balancer_test.rb
56
+ - test/actors/httpserver_test.rb
54
57
  - test/actors/log_test.rb
55
58
  - test/actors/merger_test.rb
56
59
  - test/actors/persister_test.rb
@@ -91,7 +94,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
91
94
  version: '0'
92
95
  requirements: []
93
96
  rubyforge_project: oflow
94
- rubygems_version: 2.2.0
97
+ rubygems_version: 2.2.2
95
98
  signing_key:
96
99
  specification_version: 4
97
100
  summary: Operations Workflow in Ruby