oflow 0.5.0 → 0.6.0

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.
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