asynchro 0.1.0 → 0.1.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/README.rdoc +10 -0
- data/VERSION +1 -0
- data/asynchro.gemspec +63 -0
- data/lib/asynchro/extensions.rb +9 -0
- data/lib/asynchro/state.rb +62 -0
- data/lib/asynchro/test_helper.rb +115 -0
- data/lib/asynchro/tracker.rb +52 -0
- data/test/test_asynchro_extensions.rb +35 -0
- data/test/test_asynchro_state.rb +118 -0
- data/test/test_asynchro_test_helper.rb +22 -0
- data/test/test_asynchro_tracker.rb +52 -0
- metadata +17 -7
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,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.
|
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: &
|
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: *
|
24
|
+
version_requirements: *2152224060
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: bundler
|
27
|
-
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: *
|
35
|
+
version_requirements: *2152814240
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: jeweler
|
38
|
-
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: *
|
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
|