oflow 0.3.0 → 0.4.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: 3bffd9fcf94e7e2e7bd1042d9eac1579debdbb15
4
- data.tar.gz: 1606f17e500c5c533b7192dfb2308ffffb99a393
3
+ metadata.gz: a5ebeca0ca2d46e8134511f55eeb680352c49a0a
4
+ data.tar.gz: 7aacf1ca2bde1fd1413695f607a6d087bfbc8935
5
5
  SHA512:
6
- metadata.gz: c9c6fac1a663adba34988a7caeaff5603054aaa7cf581186bcd199ff439650bc168e3ff65f9edf5a0cc2bf71e246aef5e032f97a11b70f7b2b92050170467053
7
- data.tar.gz: a158841d007f3be5f1bddc54fb22686931ff42bdd033c5b097d2bcef6ceb351347e9c0709107926866de9ea0504c429d7bbe28ffeb1d9a2b2974075bb790de80
6
+ metadata.gz: 4d8a98f7a6362f81f1fe83d56017b5cf09ced84f59be798fa60edbd647031a83772e0045ac2559d720d79b2367559e18471e24aadc80a789305932cb529c7a7b
7
+ data.tar.gz: ab9864fcea8cb033cbe1c7926ef8edee98934a741d2eae89a7db1f8a7b9e3ac85de436a50931e7110652c38ec0158530f047b0c6e0591ffe696e76465a026e29
data/README.md CHANGED
@@ -21,9 +21,19 @@ Follow [@peterohler on Twitter](http://twitter.com/#!/peterohler) for announceme
21
21
 
22
22
  ## Build Status
23
23
 
24
- [![Build Status](https://secure.travis-ci.org/ohler55/oflow.png?branch=master)](http://travis-ci.org/ohler55/oflow)
24
+ [![Build Status](https://travis-ci.org/ohler55/oflow-ruby.png?branch=master)](https://travis-ci.org/ohler55/oflow-ruby)
25
25
 
26
- ### Current Release 0.3
26
+ ## Release Notes
27
+
28
+ ### Release 0.4
29
+
30
+ - Added support for dynamic Timer options updates.
31
+
32
+ - Added Balancer Actor for load balancing of processing across multiple tasks.
33
+
34
+ - Added Merger Actor that merges two or more processing paths.
35
+
36
+ ### Release 0.3
27
37
 
28
38
  - Initial release with minimal features.
29
39
 
data/lib/oflow/actor.rb CHANGED
@@ -25,7 +25,7 @@ module OFlow
25
25
  # Returns whether the Actor should have it's own thread or not. In almost
26
26
  # all cases the Actor should have it's own thread. The exception is when the
27
27
  # action is trivial such as a relay.
28
- # @return [Boolean] indicator of whether a new thread should be created.
28
+ # @return [true|false] indicator of whether a new thread should be created.
29
29
  def with_own_thread()
30
30
  true
31
31
  end
@@ -0,0 +1,38 @@
1
+
2
+ module OFlow
3
+ module Actors
4
+
5
+ # Redirects operations to one Task out of all the linked tasks. It uses the
6
+ # Task.backed_up() method to determine which task is the least busy. It also
7
+ # attempts to distribute requests somewhat evenly if Tasks are equally as
8
+ # busy.
9
+ class Balancer < Actor
10
+
11
+ def initialize(task, options)
12
+ super
13
+ @cnt = 0
14
+ @called = {}
15
+ end
16
+
17
+ def perform(op, box)
18
+ best = nil
19
+ order = nil
20
+ bbu = nil
21
+ @task.links().each do |dest,lnk|
22
+ t = lnk.target
23
+ next if t.nil?
24
+ bu = t.backed_up()
25
+ if bbu.nil? || bu < bbu || (bbu == bu && @called.fetch(dest, 0) < order)
26
+ best = dest
27
+ bbu = bu
28
+ order = @called.fetch(dest, 0)
29
+ end
30
+ end
31
+ @cnt += 1
32
+ @called[best] = @cnt
33
+ task.ship(best, box)
34
+ end
35
+
36
+ end # Balancer
37
+ end # Actors
38
+ end # OFlow
@@ -0,0 +1,85 @@
1
+
2
+ module OFlow
3
+ module Actors
4
+
5
+ class Merger < Actor
6
+
7
+ def initialize(task, options)
8
+ super
9
+ @match_key = options.fetch(:match, :tracker)
10
+ @cnt = options.fetch(:count, 2)
11
+ # Hash of Arrays
12
+ @waiting = {}
13
+ end
14
+
15
+ def perform(op, box)
16
+ matches = match(box)
17
+ if matches.nil?
18
+ waiting_add(box)
19
+ else
20
+ matches.each { |b| waiting_remove(b) }
21
+ matches << box
22
+ box = merge(matches)
23
+ task.ship(nil, box)
24
+ end
25
+ end
26
+
27
+ def box_key(box)
28
+ key = nil
29
+ if :tracker == @match_key
30
+ key = box.tracker.id unless box.tracker.nil?
31
+ elsif !@match_key.nil?
32
+ key = box.get(@match_key)
33
+ end
34
+ key
35
+ end
36
+
37
+ def waiting_add(box)
38
+ key = box_key(box)
39
+ boxes = @waiting[key]
40
+ if boxes.nil?
41
+ @waiting[key] = [box]
42
+ else
43
+ boxes << box
44
+ end
45
+ end
46
+
47
+ def waiting_remove(box)
48
+ key = box_key(box)
49
+ boxes = @waiting[key]
50
+ # only remove the box, not a similar one or one that is ==
51
+ boxes.delete_if { |b| box.equal?(b) }
52
+ end
53
+
54
+ # Should look at all the waiting boxes and determine which of those if any
55
+ # are a match for the target. If all necessary matches are found then an
56
+ # array of the boxes are returned, otherwise nil is returned.
57
+ def match(target)
58
+ key = box_key(target)
59
+ boxes = @waiting[key]
60
+ return nil if boxes.nil? || (boxes.size + 1) < @cnt
61
+ Array.new(boxes)
62
+ end
63
+
64
+ # Should merge the boxes and return a single box. The default is to take
65
+ # all the box contents and place them in an Array and then merge the
66
+ # Trackers if there are any.
67
+ def merge(boxes)
68
+ content = []
69
+ tracker = nil
70
+ boxes.each do |b|
71
+ content << b.contents
72
+ unless b.tracker.nil?
73
+ if tracker.nil?
74
+ tracker = b.tracker
75
+ else
76
+ tracker = tracker.merge(b.tracker)
77
+ end
78
+ end
79
+ end
80
+ Box.new(content, tracker)
81
+ end
82
+
83
+ end # Merger
84
+ end # Actors
85
+ end # OFlow
@@ -30,8 +30,10 @@ module OFlow
30
30
  def initialize(task, options={})
31
31
  @count = 0
32
32
  @pending = nil
33
+ @stop = nil
34
+ @period = nil
35
+ @repeat = nil
33
36
  set_options(options)
34
- @start = Time.now() if @start.nil?
35
37
  @pending = @start
36
38
  super
37
39
  task.receive(:init, nil)
@@ -46,17 +48,25 @@ module OFlow
46
48
  op = op.to_sym unless op.nil?
47
49
  case op
48
50
  when :stop
49
- # TBD if no arg (or earlier than now) then stop now else set to new stop time
51
+ set_stop(box.nil? ? nil : box.contents)
50
52
  when :start
51
- # TBD if stopped then start if no arg, if arg then set start time
53
+ old = @start
54
+ set_start(box.nil? ? nil : box.contents)
55
+ @pending = @start if @start < old
52
56
  when :period
53
- # TBD
57
+ old = @period
58
+ set_period(box.nil? ? nil : box.contents)
59
+ if old.nil? || @pending.nil? || @pending.nil?
60
+ @pending = nil
61
+ else
62
+ @pending = @pending - old + @period
63
+ end
54
64
  when :repeat
55
- # TBD
65
+ set_repeat(box.nil? ? nil : box.contents)
56
66
  when :label
57
- # TBD
67
+ set_label(box.nil? ? nil : box.contents)
58
68
  when :with_tracker
59
- # TBD
69
+ set_with_tracker(box.nil? ? nil : box.contents)
60
70
  end
61
71
  while true
62
72
  now = Time.now()
@@ -76,7 +86,7 @@ module OFlow
76
86
  unless Task::STOPPED == task.state
77
87
  @count += 1
78
88
  now = Time.now()
79
- tracker = @with_tracker ? Tracker.new(@label) : nil
89
+ tracker = @with_tracker ? Tracker.create(@label) : nil
80
90
  box = Box.new([@label, @count, now.utc()], tracker)
81
91
  task.links.each_key do |key|
82
92
  begin
@@ -112,13 +122,61 @@ module OFlow
112
122
  end
113
123
 
114
124
  def set_options(options)
115
- @start = options[:start]
116
- @stop = options[:stop]
117
- @period = options[:period]
118
- @repeat = options[:repeat]
119
- @label = options[:label]
120
- @with_tracker = options[:with_tracker]
121
- # TBD check values for type and range
125
+ set_start(options[:start]) # if nil let start get set to now
126
+ set_stop( options[:stop]) if options.has_key?(:stop)
127
+ set_period(options[:period]) if options.has_key?(:period)
128
+ set_repeat(options[:repeat]) if options.has_key?(:repeat)
129
+ set_with_tracker(options[:with_tracker])
130
+ @label = options[:label].to_s
131
+ end
132
+
133
+ def set_start(v)
134
+ now = Time.now()
135
+ if v.is_a?(Numeric)
136
+ v = now + v
137
+ elsif v.nil?
138
+ v = Time.now()
139
+ elsif !v.kind_of?(Time) && !v.kind_of?(Date)
140
+ raise ConfigError.new("Expected start to be a Time or Numeric, not a #{v.class}.")
141
+ end
142
+ @start = v
143
+ end
144
+
145
+ def set_stop(v)
146
+ now = Time.now()
147
+ if v.is_a?(Numeric)
148
+ v = now + v
149
+ elsif !v.nil? && !v.kind_of?(Time) && !v.kind_of?(Date)
150
+ raise ConfigError.new("Expected stop to be a Time or Numeric, not a #{v.class}.")
151
+ end
152
+ @stop = v
153
+ end
154
+
155
+ def set_period(v)
156
+ unless v.nil? || v.kind_of?(Numeric)
157
+ raise ConfigError.new("Expected period to be a Numeric, not a #{v.class}.")
158
+ end
159
+ @period = v
160
+ end
161
+
162
+ def set_repeat(v)
163
+ unless v.nil? || v.kind_of?(Fixnum)
164
+ raise ConfigError.new("Expected repeat to be a Fixnum, not a #{v.class}.")
165
+ end
166
+ @repeat = v
167
+ end
168
+
169
+ def set_label(v)
170
+ v = v.to_s unless v.nil?
171
+ @label = v
172
+ end
173
+
174
+ def set_with_tracker(v)
175
+ v = false if v.nil?
176
+ unless true == v || false == v
177
+ raise ConfigError.new("Expected with_tracker to be a boolean, not a #{v.class}.")
178
+ end
179
+ @with_tracker = v
122
180
  end
123
181
 
124
182
  end # Timer
data/lib/oflow/actors.rb CHANGED
@@ -8,4 +8,7 @@ require 'oflow/actors/ignore'
8
8
  require 'oflow/actors/relay'
9
9
  require 'oflow/actors/log'
10
10
  require 'oflow/actors/errorhandler'
11
+
12
+ require 'oflow/actors/balancer'
13
+ require 'oflow/actors/merger'
11
14
  require 'oflow/actors/timer'
@@ -74,7 +74,7 @@ module OFlow
74
74
 
75
75
  # Performs a recursive walk over all Tasks and yields to the provided block
76
76
  # for each. Flows are followed recusively.
77
- # @param tasks_only [Boolean] indicates on Tasks and not Flows are yielded to
77
+ # @param tasks_only [true|false] indicates on Tasks and not Flows are yielded to
78
78
  # @param blk [Proc] Proc to call on each iteration
79
79
  def walk_tasks(tasks_only=true, &blk)
80
80
  @tasks.each_value do |t|
@@ -185,7 +185,7 @@ module OFlow
185
185
  end
186
186
 
187
187
  # Shuts down all Tasks.
188
- # @param flush_first [Boolean] flag indicating shutdown should occur after the system becomes idle
188
+ # @param flush_first [true|false] flag indicating shutdown should occur after the system becomes idle
189
189
  def shutdown(flush_first=false)
190
190
  # block all tasks first so threads can empty queues
191
191
  @tasks.each_value do |task|
data/lib/oflow/link.rb CHANGED
@@ -18,7 +18,7 @@ module OFlow
18
18
  # Tasks and Flows.
19
19
  # @param target_name [Symbol] target Task base name
20
20
  # @param op [Symbol] operation to use on the target
21
- # @param ingress [Boolean] indicates the Link is internal
21
+ # @param ingress [true|false] indicates the Link is internal
22
22
  # @return [Link] new Link
23
23
  def initialize(target_name, op, ingress=false)
24
24
  @target_name = target_name
data/lib/oflow/task.rb CHANGED
@@ -90,7 +90,7 @@ module OFlow
90
90
  sleep(1.0)
91
91
  end
92
92
  rescue Exception => e
93
- puts "*** #{full_name} #{e.class}: #{e.message}"
93
+ puts "*** #{full_name} #{e.class}: #{e.message}"
94
94
  @current_req = nil
95
95
  # TBD Env.rescue(e)
96
96
  end
@@ -159,7 +159,7 @@ module OFlow
159
159
  # selecting an Actor when stepping from the Inspector.
160
160
  # @return [Fixnum] a measure of how backed up a Task is
161
161
  def backed_up()
162
- cnt = @queue.size()
162
+ cnt = @queue.size() + (@current_req.nil? ? 0 : 1)
163
163
  return 0 if 0 == cnt
164
164
  if @max_queue_count.nil? || 0 == @max_queue_count
165
165
  cnt = 80 if 80 < cnt
@@ -217,6 +217,7 @@ module OFlow
217
217
 
218
218
  # Wakes up the Task if it has been stopped or if Env.shutdown() has been called.
219
219
  def wakeup()
220
+ # don't wake if the task is currently processing
220
221
  @loop.wakeup() unless @loop.nil?
221
222
  end
222
223
 
@@ -11,8 +11,15 @@ module OFlow
11
11
 
12
12
  def initialize(name, actor_class, options={})
13
13
  @name = name
14
+ @before = []
15
+ @state = options.fetch(:state, Task::RUNNING)
16
+ @starting = true
14
17
  @actor = actor_class.new(self, options)
18
+ @starting = false
15
19
  @history = []
20
+ @before.each do |req|
21
+ receive(req[0], req[1])
22
+ end
16
23
  end
17
24
 
18
25
  def reset()
@@ -23,9 +30,27 @@ module OFlow
23
30
  ":test:#{@name}"
24
31
  end
25
32
 
33
+ def state()
34
+ @state
35
+ end
36
+
37
+ def links()
38
+ lnk = Link.new(@name, nil)
39
+ lnk.instance_variable_set(:@target, self)
40
+ { nil => lnk }
41
+ end
42
+
43
+ def queue_count()
44
+ 0
45
+ end
46
+
26
47
  # Calls perform on the actor instance
27
48
  def receive(op, box)
28
- @actor.perform(op, box)
49
+ if @starting
50
+ @before << [op, box]
51
+ else
52
+ @actor.perform(op, box)
53
+ end
29
54
  end
30
55
 
31
56
  # Task API that adds entry to history.
data/lib/oflow/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
2
  module OFlow
3
3
  # Current version of the module.
4
- VERSION = '0.3.0'
4
+ VERSION = '0.4.0'
5
5
  end
@@ -0,0 +1,107 @@
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 'test/unit'
10
+ require 'oflow'
11
+ require 'oflow/test'
12
+
13
+ require 'collector'
14
+
15
+ class Busy < ::OFlow::Actor
16
+
17
+ def initialize(task, options)
18
+ super
19
+ @delay = options.fetch(:delay, 0)
20
+ end
21
+
22
+ def perform(op, box)
23
+ if 0.0 < @delay
24
+ done = Time.now() + @delay
25
+ while true
26
+ now = Time.now()
27
+ break if done <= now
28
+ sleep(done - now)
29
+ end
30
+ end
31
+ task.ship(nil, ::OFlow::Box.new([task.name, box.contents]))
32
+ end
33
+
34
+ end # Busy
35
+
36
+ class BalancerTest < ::Test::Unit::TestCase
37
+
38
+ def test_balancer_fair
39
+ balancer = nil
40
+ collector = nil
41
+ ::OFlow::Env.flow('fair') { |f|
42
+ f.task('balance', ::OFlow::Actors::Balancer) { |t|
43
+ balancer = t
44
+ t.link(:one, :one, nil)
45
+ t.link(:two, :two, nil)
46
+ t.link(:three, :three, nil)
47
+ }
48
+ f.task(:one, Busy) { |t|
49
+ t.link(nil, :collector, :one)
50
+ }
51
+ f.task(:two, Busy) { |t|
52
+ t.link(nil, :collector, :two)
53
+ }
54
+ f.task(:three, Busy) { |t|
55
+ t.link(nil, :collector, :three)
56
+ }
57
+ f.task(:collector, Collector) { |t|
58
+ collector = t.actor
59
+ }
60
+ }
61
+ 9.times { |i| balancer.receive(nil, ::OFlow::Box.new(i)) }
62
+ ::OFlow::Env.flush()
63
+ counts = {}
64
+ collector.collection.each { |a| counts[a[0]] = counts.fetch(a[0], 0) + 1 }
65
+
66
+ assert_equal(counts[:one], counts[:two], 'all counts should be the same')
67
+ assert_equal(counts[:two], counts[:three], 'all counts should be the same')
68
+
69
+ ::OFlow::Env.clear()
70
+ end
71
+
72
+ def test_balancer_less_busy
73
+ balancer = nil
74
+ collector = nil
75
+ ::OFlow::Env.flow('less-busy') { |f|
76
+ f.task('balance', ::OFlow::Actors::Balancer) { |t|
77
+ balancer = t
78
+ t.link(:one, :one, nil)
79
+ t.link(:two, :two, nil)
80
+ t.link(:three, :three, nil)
81
+ }
82
+ f.task(:one, Busy, delay: 0.01) { |t|
83
+ t.link(nil, :collector, :one)
84
+ }
85
+ f.task(:two, Busy, delay: 0.02) { |t|
86
+ t.link(nil, :collector, :two)
87
+ }
88
+ f.task(:three, Busy, delay: 0.04) { |t|
89
+ t.link(nil, :collector, :three)
90
+ }
91
+ f.task(:collector, Collector) { |t|
92
+ collector = t.actor
93
+ }
94
+ }
95
+ 40.times { |i| balancer.receive(nil, ::OFlow::Box.new(i)); sleep(0.005) }
96
+ ::OFlow::Env.flush()
97
+ counts = {}
98
+ collector.collection.each { |a| counts[a[0]] = counts.fetch(a[0], 0) + 1 }
99
+ #puts "*** #{counts}"
100
+
101
+ assert(counts[:one] > counts[:two], 'one is faster and should have processed more than two')
102
+ assert(counts[:two] > counts[:three], 'two is faster and should have processed more than three')
103
+
104
+ ::OFlow::Env.clear()
105
+ end
106
+
107
+ end # BalancerTest
@@ -0,0 +1,135 @@
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 'test/unit'
10
+ require 'oflow'
11
+ require 'oflow/test'
12
+
13
+ require 'collector'
14
+
15
+ class Splitter < ::OFlow::Actor
16
+
17
+ def initialize(task, options)
18
+ super
19
+ end
20
+
21
+ def perform(op, box)
22
+ task.ship(:left, box)
23
+ task.ship(:right, box)
24
+ end
25
+
26
+ end # Splitter
27
+
28
+ class Multiplier < ::OFlow::Actor
29
+
30
+ def initialize(task, options)
31
+ super
32
+ @factor = options.fetch(:factor, 1)
33
+ end
34
+
35
+ def perform(op, box)
36
+ box = box.set(nil, box.contents * @factor)
37
+ task.ship(nil, box)
38
+ end
39
+
40
+ end # Multiplier
41
+
42
+ class MergerTest < ::Test::Unit::TestCase
43
+
44
+ def test_merger_any
45
+ start = nil
46
+ collector = nil
47
+ ::OFlow::Env.flow('merge') { |f|
48
+ f.task(:split, Splitter) { |t|
49
+ start = t
50
+ t.link(:left, :one, nil)
51
+ t.link(:right, :two, nil)
52
+ }
53
+ f.task(:one, Multiplier, factor: 2) { |t|
54
+ t.link(nil, :merge, nil)
55
+ }
56
+ f.task(:two, Multiplier, factor: 3) { |t|
57
+ t.link(nil, :merge, nil)
58
+ }
59
+ f.task(:merge, ::OFlow::Actors::Merger) { |t|
60
+ t.link(nil, :collector, nil)
61
+ }
62
+ f.task(:collector, Collector) { |t|
63
+ collector = t.actor
64
+ }
65
+ }
66
+ start.receive(nil, ::OFlow::Box.new(1))
67
+ ::OFlow::Env.flush()
68
+
69
+ result = collector.collection[0]
70
+ assert_equal(2, result.size, 'should be 2 values in the box')
71
+ assert(result.include?(2), 'box should include 2')
72
+ assert(result.include?(3), 'box should include 3')
73
+
74
+ ::OFlow::Env.clear()
75
+ end
76
+
77
+ def test_merger_tracker
78
+ start = nil
79
+ collector = nil
80
+ ::OFlow::Env.flow('merge') { |f|
81
+ f.task(:split, Splitter) { |t|
82
+ start = t
83
+ t.link(:left, :one, nil)
84
+ t.link(:right, :two, nil)
85
+ }
86
+ f.task(:one, Multiplier, factor: 2) { |t|
87
+ t.link(nil, :merge, nil)
88
+ }
89
+ f.task(:two, Multiplier, factor: 3) { |t|
90
+ t.link(nil, :merge, nil)
91
+ }
92
+ f.task(:merge, ::OFlow::Actors::Merger) { |t|
93
+ t.link(nil, :collector, nil)
94
+ }
95
+ f.task(:collector, Collector, contents_only: false) { |t|
96
+ collector = t.actor
97
+ }
98
+ }
99
+ tracker = ::OFlow::Tracker.create('start')
100
+ start.receive(nil, ::OFlow::Box.new(1, tracker))
101
+ tracker2 = ::OFlow::Tracker.create('start2')
102
+ start.receive(nil, ::OFlow::Box.new(10, tracker2))
103
+ ::OFlow::Env.flush()
104
+
105
+ box = collector.collection[0]
106
+ result = box.contents
107
+ assert_equal(2, result.size, 'should be 2 values in the box')
108
+ assert(result.include?(2), 'box should include 2')
109
+ assert(result.include?(3), 'box should include 3')
110
+
111
+ t = box.tracker()
112
+ assert_not_nil(t, 'should have a tracker')
113
+ assert_equal(t.id, tracker.id, 'tracker id should be carried through')
114
+ track = t.track
115
+
116
+ assert_equal('start', track[0].location)
117
+ assert_equal(':merge:split', track[1].location)
118
+ split = track[2].map { |a| a.map { |stamp| stamp.location } }
119
+
120
+ assert_equal(2, split.size, 'should be 2 values in the split')
121
+ assert(split.include?([':merge:one']), 'split should include [merge:one]')
122
+ assert(split.include?([':merge:two']), 'split should include [merge:two]')
123
+ assert_equal(':merge:merge', track[3].location)
124
+ assert_equal(':merge:collector', track[4].location)
125
+
126
+ box = collector.collection[1]
127
+ result = box.contents
128
+ assert_equal(2, result.size, 'should be 2 values in the box')
129
+ assert(result.include?(20), 'box should include 20')
130
+ assert(result.include?(30), 'box should include 30')
131
+
132
+ ::OFlow::Env.clear()
133
+ end
134
+
135
+ end # MergerTest
@@ -8,12 +8,13 @@
8
8
 
9
9
  require 'test/unit'
10
10
  require 'oflow'
11
+ require 'oflow/test'
11
12
 
12
13
  require 'collector'
13
14
 
14
15
  class TimerTest < ::Test::Unit::TestCase
15
16
 
16
- def test_timer_repeat
17
+ def test_timer_period_repeat
17
18
  period = 0.1
18
19
  timer = nil
19
20
  collector = nil
@@ -51,6 +52,232 @@ class TimerTest < ::Test::Unit::TestCase
51
52
  ::OFlow::Env.clear()
52
53
  end
53
54
 
54
- # TBD more tests on start, stop, combinations of options, and error conditions
55
+ def test_timer_options_start
56
+ now = Time.now()
57
+ t = ::OFlow::Test::ActorWrap.new('test', ::OFlow::Actors::Timer, start: now, state: ::OFlow::Task::BLOCKED)
58
+ assert_equal(now, t.actor.start, 'is the start time now?')
59
+
60
+ t = ::OFlow::Test::ActorWrap.new('test', ::OFlow::Actors::Timer, start: nil, state: ::OFlow::Task::BLOCKED)
61
+ assert_equal(Time, t.actor.start.class, 'is the start time a Time?')
62
+ assert(0.1 > (Time.now() - t.actor.start), 'is the start time now?')
63
+
64
+ t = ::OFlow::Test::ActorWrap.new('test', ::OFlow::Actors::Timer, start: 2, state: ::OFlow::Task::BLOCKED)
65
+ assert_equal(Time, t.actor.start.class, 'is the start time a Time?')
66
+ assert(0.1 > (Time.now() + 2 - t.actor.start), 'is the start time now + 2?')
67
+
68
+ assert_raise(::OFlow::ConfigError) do
69
+ ::OFlow::Test::ActorWrap.new('test', ::OFlow::Actors::Timer, start: 'now')
70
+ end
71
+ end
72
+
73
+ def test_timer_options_stop
74
+ now = Time.now()
75
+ t = ::OFlow::Test::ActorWrap.new('test', ::OFlow::Actors::Timer, stop: now, state: ::OFlow::Task::BLOCKED)
76
+ assert_equal(now, t.actor.stop, 'is the stop time now?')
77
+
78
+ t = ::OFlow::Test::ActorWrap.new('test', ::OFlow::Actors::Timer, stop: nil, state: ::OFlow::Task::BLOCKED)
79
+ assert_equal(nil, t.actor.stop, 'is the stop time nil?')
80
+
81
+ t = ::OFlow::Test::ActorWrap.new('test', ::OFlow::Actors::Timer, stop: 2, state: ::OFlow::Task::BLOCKED)
82
+ assert_equal(Time, t.actor.stop.class, 'is the stop time a Time?')
83
+ assert(0.1 > (Time.now() + 2 - t.actor.stop), 'is the stop time now + 2?')
84
+
85
+ assert_raise(::OFlow::ConfigError) do
86
+ ::OFlow::Test::ActorWrap.new('test', ::OFlow::Actors::Timer, stop: 'now')
87
+ end
88
+ end
89
+
90
+ def test_timer_options_period
91
+ t = ::OFlow::Test::ActorWrap.new('test', ::OFlow::Actors::Timer, period: nil, state: ::OFlow::Task::BLOCKED)
92
+ assert_equal(nil, t.actor.period, 'is the period nil?')
93
+
94
+ t = ::OFlow::Test::ActorWrap.new('test', ::OFlow::Actors::Timer, period: 2, state: ::OFlow::Task::BLOCKED)
95
+ assert_equal(2, t.actor.period, 'is the period 2?')
96
+
97
+ t = ::OFlow::Test::ActorWrap.new('test', ::OFlow::Actors::Timer, period: 2.0, state: ::OFlow::Task::BLOCKED)
98
+ assert_equal(2.0, t.actor.period, 'is the period 2.0?')
99
+
100
+ assert_raise(::OFlow::ConfigError) do
101
+ ::OFlow::Test::ActorWrap.new('test', ::OFlow::Actors::Timer, period: 'now')
102
+ end
103
+ end
104
+
105
+ def test_timer_options_repeat
106
+ t = ::OFlow::Test::ActorWrap.new('test', ::OFlow::Actors::Timer, repeat: nil, state: ::OFlow::Task::BLOCKED)
107
+ assert_equal(nil, t.actor.repeat, 'is the repeat nil?')
108
+
109
+ t = ::OFlow::Test::ActorWrap.new('test', ::OFlow::Actors::Timer, repeat: 2, state: ::OFlow::Task::BLOCKED)
110
+ assert_equal(2, t.actor.repeat, 'is the repeat 2?')
111
+
112
+ assert_raise(::OFlow::ConfigError) do
113
+ ::OFlow::Test::ActorWrap.new('test', ::OFlow::Actors::Timer, repeat: 2.0)
114
+ end
115
+ end
116
+
117
+ def test_timer_options_with_tracker
118
+ t = ::OFlow::Test::ActorWrap.new('test', ::OFlow::Actors::Timer, with_tracker: nil, state: ::OFlow::Task::BLOCKED)
119
+ assert_equal(false, t.actor.with_tracker, 'is the with_tracker false?')
120
+
121
+ t = ::OFlow::Test::ActorWrap.new('test', ::OFlow::Actors::Timer, with_tracker: false, state: ::OFlow::Task::BLOCKED)
122
+ assert_equal(false, t.actor.with_tracker, 'is the with_tracker false?')
123
+
124
+ t = ::OFlow::Test::ActorWrap.new('test', ::OFlow::Actors::Timer, with_tracker: true, state: ::OFlow::Task::BLOCKED)
125
+ assert_equal(true, t.actor.with_tracker, 'is the with_tracker true?')
126
+
127
+ assert_raise(::OFlow::ConfigError) do
128
+ ::OFlow::Test::ActorWrap.new('test', ::OFlow::Actors::Timer, with_tracker: 'now')
129
+ end
130
+
131
+ t = ::OFlow::Test::ActorWrap.new('test', ::OFlow::Actors::Timer, with_tracker: true, repeat: 2, label: 'fast')
132
+ assert_equal(2, t.history.size, 'are there 2 items in the history?')
133
+ assert_equal(false, t.history[0].box.tracker.nil?, 'is there a tracker on the box that shipped?')
134
+ end
135
+
136
+ def test_timer_repeat
137
+ t = ::OFlow::Test::ActorWrap.new('test', ::OFlow::Actors::Timer, repeat: 2, label: 'fast')
138
+ assert_equal(2, t.history.size, 'are there 2 items in the history?')
139
+ end
140
+
141
+ def test_timer_time
142
+ t = ::OFlow::Test::ActorWrap.new('test', ::OFlow::Actors::Timer, stop: 2, period: 0.5, label: 'time')
143
+ assert_equal(4, t.history.size, 'are there 4 items in the history?')
144
+ end
145
+
146
+ def test_timer_perform_period
147
+ period = 0.1
148
+ timer = nil
149
+ collector = nil
150
+ ::OFlow::Env.flow('one-time') { |f|
151
+ f.task('once', ::OFlow::Actors::Timer, repeat: 2, period: 2) { |t|
152
+ timer = t
153
+ t.link(:ping, :collector, :tick)
154
+ }
155
+ f.task(:collector, Collector) { |t|
156
+ collector = t.actor
157
+ }
158
+ }
159
+ timer.receive(:period, ::OFlow::Box.new(period))
160
+ ::OFlow::Env.flush()
161
+ prev = nil
162
+ ticks = collector.collection.map do |t|
163
+ tf = t[2].to_f
164
+ if prev.nil?
165
+ tick = [t[1], tf, 0.0]
166
+ else
167
+ tick = [t[1], tf, tf - prev]
168
+ end
169
+ prev = tf
170
+ tick
171
+ end
172
+
173
+ ticks.size.times do |i|
174
+ tick = ticks[i]
175
+ assert_equal(i + 1, tick[0])
176
+ next if 0 == i
177
+ dif = tick[2] - period
178
+ limit = period / 10 # 10% accuracy
179
+ assert(-limit < dif && dif < limit, "Verify timer fires are within 10% of expected. (dif: #{dif}, limit: #{limit})")
180
+ end
181
+
182
+ ::OFlow::Env.clear()
183
+ end
184
+
185
+ def test_timer_perform_repeat
186
+ repeat = 2
187
+ timer = nil
188
+ collector = nil
189
+ ::OFlow::Env.flow('one-time') { |f|
190
+ f.task('once', ::OFlow::Actors::Timer, repeat: 4, period: 0.1) { |t|
191
+ timer = t
192
+ t.link(:ping, :collector, :tick)
193
+ }
194
+ f.task(:collector, Collector) { |t|
195
+ collector = t.actor
196
+ }
197
+ }
198
+ timer.receive(:repeat, ::OFlow::Box.new(repeat))
199
+ ::OFlow::Env.flush()
200
+ assert_equal(2, collector.collection.size)
201
+
202
+ ::OFlow::Env.clear()
203
+ end
204
+
205
+ def test_timer_perform_start
206
+ now = Time.now()
207
+ timer = nil
208
+ collector = nil
209
+ ::OFlow::Env.flow('one-time') { |f|
210
+ f.task('once', ::OFlow::Actors::Timer, repeat: 1, period: 0.1, start: 2) { |t|
211
+ timer = t
212
+ t.link(:ping, :collector, :tick)
213
+ }
214
+ f.task(:collector, Collector) { |t|
215
+ collector = t.actor
216
+ }
217
+ }
218
+ timer.receive(:start, ::OFlow::Box.new(nil))
219
+ ::OFlow::Env.flush()
220
+ first_fire = collector.collection[0][2] - now
221
+ assert(0.01 > first_fire, "first fire was at #{first_fire}, expected less than 0.01 msecs?")
222
+
223
+ ::OFlow::Env.clear()
224
+ end
225
+
226
+ def test_timer_perform_stop
227
+ timer = nil
228
+ collector = nil
229
+ ::OFlow::Env.flow('one-time') { |f|
230
+ f.task('once', ::OFlow::Actors::Timer, repeat: 10, period: 0.1, stop: 2) { |t|
231
+ timer = t
232
+ t.link(:ping, :collector, :tick)
233
+ }
234
+ f.task(:collector, Collector) { |t|
235
+ collector = t.actor
236
+ }
237
+ }
238
+ timer.receive(:stop, ::OFlow::Box.new(0.25))
239
+ ::OFlow::Env.flush()
240
+ assert_equal(3, collector.collection.size)
241
+
242
+ ::OFlow::Env.clear()
243
+ end
244
+
245
+ def test_timer_perform_label
246
+ timer = nil
247
+ collector = nil
248
+ ::OFlow::Env.flow('one-time') { |f|
249
+ f.task('once', ::OFlow::Actors::Timer, repeat: 2, period: 0.1, label: 'first') { |t|
250
+ timer = t
251
+ t.link(:ping, :collector, :tick)
252
+ }
253
+ f.task(:collector, Collector) { |t|
254
+ collector = t.actor
255
+ }
256
+ }
257
+ timer.receive(:label, ::OFlow::Box.new('second'))
258
+ ::OFlow::Env.flush()
259
+ assert_equal(['first', 'second'], collector.collection.map { |x| x[0] })
260
+
261
+ ::OFlow::Env.clear()
262
+ end
263
+
264
+ def test_timer_perform_tracker
265
+ timer = nil
266
+ collector = nil
267
+ ::OFlow::Env.flow('one-time') { |f|
268
+ f.task('once', ::OFlow::Actors::Timer, repeat: 2, period: 0.1, with_tracker: false) { |t|
269
+ timer = t
270
+ t.link(:ping, :collector, :tick)
271
+ }
272
+ f.task(:collector, Collector, contents_only: false) { |t|
273
+ collector = t.actor
274
+ }
275
+ }
276
+ timer.receive(:with_tracker, ::OFlow::Box.new(true))
277
+ ::OFlow::Env.flush()
278
+ assert_equal([false, true], collector.collection.map { |x| !x.tracker.nil? })
279
+
280
+ ::OFlow::Env.clear()
281
+ end
55
282
 
56
283
  end # TimerTest
data/test/all_tests.rb CHANGED
@@ -24,4 +24,6 @@ require 'flow_tracker_test'
24
24
  # Actor tests
25
25
  require 'actors/log_test'
26
26
  require 'actors/timer_test'
27
+ require 'actors/balancer_test'
28
+ require 'actors/merger_test'
27
29
 
data/test/collector.rb CHANGED
@@ -13,10 +13,15 @@ class Collector < ::OFlow::Actor
13
13
  def initialize(task, options)
14
14
  super
15
15
  @collection = []
16
+ @contents_only = options.fetch(:contents_only, true)
16
17
  end
17
18
 
18
19
  def perform(op, box)
19
- @collection << box.contents
20
+ if @contents_only
21
+ @collection << box.contents
22
+ else
23
+ @collection << box
24
+ end
20
25
  end
21
26
 
22
27
  end # Collector
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.3.0
4
+ version: 0.4.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-04 00:00:00.000000000 Z
11
+ date: 2014-02-08 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.
@@ -23,9 +23,11 @@ files:
23
23
  - lib/oflow.rb
24
24
  - lib/oflow/actor.rb
25
25
  - lib/oflow/actors.rb
26
+ - lib/oflow/actors/balancer.rb
26
27
  - lib/oflow/actors/errorhandler.rb
27
28
  - lib/oflow/actors/ignore.rb
28
29
  - lib/oflow/actors/log.rb
30
+ - lib/oflow/actors/merger.rb
29
31
  - lib/oflow/actors/relay.rb
30
32
  - lib/oflow/actors/timer.rb
31
33
  - lib/oflow/box.rb
@@ -47,7 +49,9 @@ files:
47
49
  - lib/oflow/test/actorwrap.rb
48
50
  - lib/oflow/tracker.rb
49
51
  - lib/oflow/version.rb
52
+ - test/actors/balancer_test.rb
50
53
  - test/actors/log_test.rb
54
+ - test/actors/merger_test.rb
51
55
  - test/actors/timer_test.rb
52
56
  - test/actorwrap_test.rb
53
57
  - test/all_tests.rb