asynchro 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -10,6 +10,16 @@ The two primary components are:
10
10
  * A simple state machine defining tool that can be used to map out the steps
11
11
  required to complete an operation. This is especially useful if the process
12
12
  will vary depending on certain conditions.
13
+
14
+ == Installation
15
+
16
+ The gem should be a good place to start:
17
+
18
+ gem install asynchro
19
+
20
+ The best approach is to put this in your Gemfile:
21
+
22
+ gem 'asynchro'
13
23
 
14
24
  == State
15
25
 
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.1
data/asynchro.gemspec ADDED
@@ -0,0 +1,63 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "asynchro"
8
+ s.version = "0.1.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Scott Tadman"]
12
+ s.date = "2011-09-26"
13
+ s.description = "Provides a number of tools to help make developing and testing asynchronous applications more manageable."
14
+ s.email = "github@tadman.ca"
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ "Gemfile",
22
+ "LICENSE.txt",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "asynchro.gemspec",
27
+ "lib/asynchro.rb",
28
+ "lib/asynchro/extensions.rb",
29
+ "lib/asynchro/state.rb",
30
+ "lib/asynchro/test_helper.rb",
31
+ "lib/asynchro/tracker.rb",
32
+ "test/helper.rb",
33
+ "test/test_asynchro.rb",
34
+ "test/test_asynchro_extensions.rb",
35
+ "test/test_asynchro_state.rb",
36
+ "test/test_asynchro_test_helper.rb",
37
+ "test/test_asynchro_tracker.rb"
38
+ ]
39
+ s.homepage = "http://github.com/tadman/asynchro"
40
+ s.licenses = ["MIT"]
41
+ s.require_paths = ["lib"]
42
+ s.rubygems_version = "1.8.10"
43
+ s.summary = "Ruby EventMachine Async Programming Toolkit"
44
+
45
+ if s.respond_to? :specification_version then
46
+ s.specification_version = 3
47
+
48
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
49
+ s.add_runtime_dependency(%q<eventmachine>, [">= 0"])
50
+ s.add_development_dependency(%q<bundler>, [">= 0"])
51
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
52
+ else
53
+ s.add_dependency(%q<eventmachine>, [">= 0"])
54
+ s.add_dependency(%q<bundler>, [">= 0"])
55
+ s.add_dependency(%q<jeweler>, [">= 0"])
56
+ end
57
+ else
58
+ s.add_dependency(%q<eventmachine>, [">= 0"])
59
+ s.add_dependency(%q<bundler>, [">= 0"])
60
+ s.add_dependency(%q<jeweler>, [">= 0"])
61
+ end
62
+ end
63
+
@@ -0,0 +1,9 @@
1
+ module Asynchro::Extensions
2
+ def async_chain(&block)
3
+ Asynchro::Tracker.new(&block)
4
+ end
5
+
6
+ def async_state(&block)
7
+ Asynchro::State.new(&block)
8
+ end
9
+ end
@@ -0,0 +1,62 @@
1
+ class Asynchro::State
2
+ # Create a state map object. If a block is given, it is called with the
3
+ # instance created, then that instance is run.
4
+ def initialize(&block)
5
+ @states = {
6
+ :finish => [ ]
7
+ }
8
+
9
+ if (block_given?)
10
+ case (block.arity)
11
+ when 0
12
+ instance_exec(&block)
13
+ else
14
+ block.call(self)
15
+ end
16
+
17
+ self.run!
18
+ end
19
+ end
20
+
21
+ # Declares the action to be taken when a particular state is entered. The
22
+ # argument is the state name and should be a Symbol. A block must be
23
+ # provided. If this state is already declared, the given block will be
24
+ # executed after the previous declarations.
25
+ def declare(state)
26
+ (@states[state.to_sym] ||= [ ]) << Proc.new
27
+ end
28
+
29
+ # Returns true if a particular state has been declared, false otherwise.
30
+ def declared?(state)
31
+ !!@states[state]
32
+ end
33
+
34
+ # Runs a particular state, or if the state is not specified, then :start
35
+ # by default.
36
+ def run!(state = :start)
37
+ procs = @states[state.to_sym]
38
+
39
+ case (state)
40
+ when :start
41
+ procs = [ lambda { finish! } ] unless (procs)
42
+ end
43
+
44
+ procs and procs.each(&:call) or STDERR.puts "WARNING: No state #{state} defined."
45
+ end
46
+
47
+ protected
48
+ # This lets the object instance function as a simple DSL by allowing
49
+ # arbitrary method names to map to various functions.
50
+ def method_missing(name, &block)
51
+ name_s = name.to_s
52
+
53
+ case (name)
54
+ when /\?$/
55
+ self.declared?(name_s.sub(/\?$/, ''))
56
+ when /\!$/
57
+ self.run!(name_s.sub(/\!$/, ''))
58
+ else
59
+ self.declare(name, &block)
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,115 @@
1
+ require 'eventmachine'
2
+
3
+ module Asynchro::TestHelper
4
+ # Wraps a given block in an EventMachine run loop. Because this loop will
5
+ # monopolize the thread, a new thread is created to execute the supplied
6
+ # block. Exceptions are trapped from both the supplied block and the
7
+ # engine and raised accordingly.
8
+ def eventmachine
9
+ exception = nil
10
+
11
+ Thread.new do
12
+ Thread.abort_on_exception = true
13
+
14
+ # Create a thread for the engine to run on
15
+ begin
16
+ EventMachine.run do
17
+ Thread.new do
18
+ # Execute the test code in a separate thread to avoid blocking
19
+ # the EventMachine loop.
20
+ begin
21
+ yield
22
+ rescue Object => exception
23
+ ensure
24
+ begin
25
+ EventMachine.stop_event_loop
26
+ rescue Object
27
+ # Shutting down may trigger an exception from time to time
28
+ # if the engine itself has failed.
29
+ end
30
+ end
31
+ end
32
+ end
33
+ rescue Object => exception
34
+ end
35
+ end.join
36
+
37
+ if (exception)
38
+ raise exception
39
+ end
40
+ end
41
+
42
+ # Asserts that a callback is made within a specified time, or will wait
43
+ # forever for the callback to occur if no time is specified. The supplied
44
+ # block will be called with the callback Proc which should be executed
45
+ # using `call` exactly once.
46
+ def assert_callback(time = nil, message = nil)
47
+ called_back = false
48
+
49
+ Pigeonrocket.execute_in_main_thread do
50
+ yield(lambda { called_back = true })
51
+ end
52
+
53
+ start_time = Time.now.to_i
54
+
55
+ while (!called_back)
56
+ select(nil, nil, nil, 0.1)
57
+
58
+ if (time and (Time.now.to_i - start_time > time))
59
+ flunk(message || 'assert_callback timed out')
60
+ end
61
+ end
62
+ end
63
+
64
+ # Asserts that a callback is made within a specified time, or will wait
65
+ # forever for the callback to occur if no time is specified. The supplied
66
+ # block will be called with the callback Proc which should be executed
67
+ # using `call` as many times as specified, or 1 by default.
68
+ def assert_callback_times(count = 1, time = nil, message = nil)
69
+ called_back = 0
70
+
71
+ Pigeonrocket.execute_in_main_thread do
72
+ yield(lambda { called_back += 1 })
73
+ end
74
+
75
+ start_time = Time.now.to_i
76
+
77
+ while (called_back < count)
78
+ select(nil, nil, nil, 0.1)
79
+
80
+ if (time and (Time.now.to_i - start_time > time))
81
+ flunk(message || "assert_callback_times timed out at only #{count} times")
82
+ end
83
+ end
84
+ end
85
+
86
+ # Tests and re-tests assertions until all of them will pass. An optional
87
+ # limit on time can be specified in seconds. A short delay is introduced
88
+ # between tests to avoid monopolizing the CPU during testing. When the
89
+ # supplied block fails to produce an exception it is presumed to have been
90
+ # a successful test and execution will continue as normal.
91
+ def assert_eventually(time = nil, message = nil)
92
+ end_time = (time and Time.now + time)
93
+ exception = nil
94
+
95
+ while (true)
96
+ begin
97
+ yield
98
+ rescue Object => e
99
+ exception = e
100
+ else
101
+ break
102
+ end
103
+
104
+ select(nil, nil, nil, 0.1)
105
+
106
+ if (end_time and Time.now > end_time)
107
+ if (exception)
108
+ raise exception
109
+ else
110
+ flunk(message || 'assert_eventually timed out')
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,52 @@
1
+ class Asynchro::Tracker
2
+ # Creates a new tracker. If a block is given, this block is called with
3
+ # the new instance as an argument, and the tracker is automatically run.
4
+ def initialize
5
+ @sequence = 0
6
+
7
+ if (block_given?)
8
+ instance_eval(&Proc.new)
9
+ self.run!
10
+ end
11
+ end
12
+
13
+ # Runs through the tasks to perform for this tracker. Should only be
14
+ # called if this object is initialized without a supplied block as in that
15
+ # case, this would have been called already.
16
+ def run!
17
+ @procs and @procs.each(&:call)
18
+ end
19
+
20
+ # Executes this block when all the actions to be performed have checked in
21
+ # as finsished.
22
+ def finish
23
+ @finish ||= [ ]
24
+ @finish << Proc.new
25
+ end
26
+
27
+ # Performs an action. The supplied block will be called with a callback
28
+ # tracking Proc that should be triggered with `call` as many times as
29
+ # are specified in the `count` argument. When the correct number of calls
30
+ # have been made, this action is considered finished.
31
+ def perform(count = 1, &block)
32
+ @blocks ||= { }
33
+
34
+ _sequence = @sequence += 1
35
+ @blocks[_sequence] = count
36
+
37
+ callback = lambda {
38
+ if (@blocks[_sequence])
39
+ if ((@blocks[_sequence] -=1) <= 0)
40
+ @blocks.delete(_sequence)
41
+ end
42
+
43
+ if (@blocks.empty?)
44
+ @finish and @finish.each(&:call)
45
+ end
46
+ end
47
+ }
48
+
49
+ @procs ||= [ ]
50
+ @procs << lambda { block.call(callback) }
51
+ end
52
+ end
@@ -0,0 +1,35 @@
1
+ require 'helper'
2
+
3
+ class TestAsynchroExtensions < Test::Unit::TestCase
4
+ include Asynchro::Extensions
5
+
6
+ def test_async_chain
7
+ chain = nil
8
+
9
+ async_chain do |_chain|
10
+ chain = _chain
11
+ end
12
+
13
+ assert_equal Asynchro::Tracker, chain.class
14
+ end
15
+
16
+ def test_async_state_explicit
17
+ state = nil
18
+
19
+ async_state do |_state|
20
+ state = _state
21
+ end
22
+
23
+ assert_equal Asynchro::State, state.class
24
+ end
25
+
26
+ def test_async_state_implicit
27
+ state = nil
28
+
29
+ async_state do
30
+ state = self
31
+ end
32
+
33
+ assert_equal Asynchro::State, state.class
34
+ end
35
+ end
@@ -0,0 +1,118 @@
1
+ require 'helper'
2
+
3
+ class TestAsynchroState < Test::Unit::TestCase
4
+ def test_empty
5
+ ran = false
6
+ instance = nil
7
+
8
+ Asynchro::State.new do
9
+ ran = true
10
+ instance = self
11
+ end
12
+
13
+ assert_eventually(5) do
14
+ assert_equal true, ran
15
+ assert_equal Asynchro::State, instance.class
16
+ end
17
+ end
18
+
19
+ def test_declared_names
20
+ state = Asynchro::State.new
21
+ ran = [ ]
22
+
23
+ state.declare(:start) do
24
+ ran << :start
25
+ state.run!(:state1)
26
+ end
27
+
28
+ state.declare(:state1) do
29
+ ran << :state1
30
+ state.run!(:state2)
31
+ end
32
+
33
+ state.declare(:state2) do
34
+ ran << :state2
35
+ state.run!(:state3)
36
+ end
37
+
38
+ state.declare(:state3) do
39
+ ran << :state3
40
+ state.finish!
41
+ end
42
+
43
+ state.run!
44
+
45
+ assert_eventually(5) do
46
+ assert_equal [ :start, :state1, :state2, :state3 ], ran
47
+ end
48
+ end
49
+
50
+ def test_implicit_names
51
+ ran = [ ]
52
+
53
+ Asynchro::State.new do
54
+ start do
55
+ ran << :start
56
+ state1!
57
+ end
58
+
59
+ state1 do
60
+ ran << :state1
61
+ state2!
62
+ end
63
+
64
+ state2 do
65
+ ran << :state2
66
+ state3!
67
+ end
68
+
69
+ state3 do
70
+ ran << :state3
71
+ finish!
72
+ end
73
+
74
+ finish do
75
+ ran << :finish
76
+ end
77
+ end
78
+
79
+ assert_eventually(5) do
80
+ assert_equal [ :start, :state1, :state2, :state3, :finish ], ran
81
+ end
82
+ end
83
+
84
+ def test_arbitrary_names
85
+ state = Asynchro::State.new
86
+ ran = [ ]
87
+
88
+ state.start do
89
+ ran << :start
90
+ state.state1!
91
+ end
92
+
93
+ state.state1 do
94
+ ran << :state1
95
+ state.state2!
96
+ end
97
+
98
+ state.state2 do
99
+ ran << :state2
100
+ state.state3!
101
+ end
102
+
103
+ state.state3 do
104
+ ran << :state3
105
+ state.finish!
106
+ end
107
+
108
+ state.finish do
109
+ ran << :finish
110
+ end
111
+
112
+ state.run!
113
+
114
+ assert_eventually(5) do
115
+ assert_equal [ :start, :state1, :state2, :state3, :finish ], ran
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,22 @@
1
+ require 'helper'
2
+
3
+ class TestAsynchroTestHelper < Test::Unit::TestCase
4
+ def test_eventmachine
5
+ ran = false
6
+
7
+ eventmachine do
8
+ assert EventMachine.reactor_running?
9
+ assert !EventMachine.reactor_thread?
10
+
11
+ EventMachine.add_timer(1) do
12
+ ran = true
13
+ end
14
+
15
+ while (!ran)
16
+ # Spin-lock here while waiting for flag to be set in reactor thread
17
+ end
18
+ end
19
+
20
+ assert_equal true, ran
21
+ end
22
+ end
@@ -0,0 +1,52 @@
1
+ require 'helper'
2
+
3
+ class TestAsynchroTracker < Test::Unit::TestCase
4
+ def test_async_chain_explicit
5
+ count = 0
6
+
7
+ Asynchro::Tracker.new do |chain|
8
+ chain.perform do |done|
9
+ chain.perform do |done|
10
+ count += 2
11
+ done.call
12
+ end
13
+
14
+ count += 1
15
+ done.call
16
+ end
17
+
18
+ chain.perform(4) do |done|
19
+ 4.times do
20
+ count += 1
21
+ done.call
22
+ end
23
+ end
24
+
25
+ chain.finish do
26
+ success = true
27
+ end
28
+ end
29
+
30
+ assert_eventually(5) do
31
+ count == 7
32
+ end
33
+ end
34
+
35
+ def test_async_chain_implicit
36
+ success = false
37
+
38
+ Asynchro::Tracker.new do
39
+ perform do |done|
40
+ done.call
41
+ end
42
+
43
+ finish do
44
+ success = true
45
+ end
46
+ end
47
+
48
+ assert_eventually(5) do
49
+ success
50
+ end
51
+ end
52
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: asynchro
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -13,7 +13,7 @@ date: 2011-09-26 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: eventmachine
16
- requirement: &2152536540 !ruby/object:Gem::Requirement
16
+ requirement: &2152224060 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *2152536540
24
+ version_requirements: *2152224060
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: bundler
27
- requirement: &2152535960 !ruby/object:Gem::Requirement
27
+ requirement: &2152814240 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *2152535960
35
+ version_requirements: *2152814240
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: jeweler
38
- requirement: &2152535400 !ruby/object:Gem::Requirement
38
+ requirement: &2153229120 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,7 +43,7 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *2152535400
46
+ version_requirements: *2153229120
47
47
  description: Provides a number of tools to help make developing and testing asynchronous
48
48
  applications more manageable.
49
49
  email: github@tadman.ca
@@ -58,9 +58,19 @@ files:
58
58
  - LICENSE.txt
59
59
  - README.rdoc
60
60
  - Rakefile
61
+ - VERSION
62
+ - asynchro.gemspec
61
63
  - lib/asynchro.rb
64
+ - lib/asynchro/extensions.rb
65
+ - lib/asynchro/state.rb
66
+ - lib/asynchro/test_helper.rb
67
+ - lib/asynchro/tracker.rb
62
68
  - test/helper.rb
63
69
  - test/test_asynchro.rb
70
+ - test/test_asynchro_extensions.rb
71
+ - test/test_asynchro_state.rb
72
+ - test/test_asynchro_test_helper.rb
73
+ - test/test_asynchro_tracker.rb
64
74
  homepage: http://github.com/tadman/asynchro
65
75
  licenses:
66
76
  - MIT