rspec-eventmachine 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # RSpec::EventMachine
2
+
3
+ This library provides some extensions to RSpec that make testing
4
+ EventMachine-based applications easier. It includes functionality for running
5
+ asynchronous code in tests, and for stubbing out the EventMachine timer methods.
6
+
7
+
8
+ ## Installation
9
+
10
+ ```
11
+ $ gem install rspec-eventmachine
12
+ ```
13
+
14
+
15
+ ## Usage
16
+
17
+ The library includes two main modules: `AsyncSteps` facilities the writing of
18
+ tests with async logic, and `FakeClock` stubs out timer methods.
19
+
20
+
21
+ ### `AsyncSteps`
22
+
23
+ To keep async tests clean, it helps to implement all the separate logical steps
24
+ in them as separate functions. Using `AsyncSteps`, you write a module
25
+ containing definitions of all your test steps, where each method takes a
26
+ callback. You then mix this module into your tests, and you can use the methods
27
+ without callbacks to write terse, flat tests.
28
+
29
+ ```rb
30
+ require "rspec/em"
31
+
32
+ MathSteps = RSpec::EM.async_steps do
33
+ def add(x, y, &callback)
34
+ EM.add_timer 0.1 do
35
+ @result = x + y
36
+ callback.call
37
+ end
38
+ end
39
+
40
+ def multiply(f, &callback)
41
+ EM.add_timer 0.2 do
42
+ @result = @result * f
43
+ callback.call
44
+ end
45
+ end
46
+
47
+ def check_result(n, &callback)
48
+ @result.should == n
49
+ callback.call
50
+ end
51
+ end
52
+
53
+ describe "Math" do
54
+ include MathSteps
55
+
56
+ it "adds and multiplies" do
57
+ add 3, 4
58
+ multiply 5
59
+ check_result 35
60
+ end
61
+ end
62
+ ```
63
+
64
+ Note that you must put all the logic in the async steps module. The method calls
65
+ in your `before`, `after` and `it` blocks are really just adding the named
66
+ methods and arguments to a queue, which `AsyncSteps` executes sequentially by
67
+ calling each method when the previous one invokes its callback. `AsyncSteps`
68
+ also makes sure that the reactor is running for the duration of each test, and
69
+ catches and reports any errors or failures that happen during the test.
70
+
71
+
72
+ ### `FakeClock`
73
+
74
+ The `FakeClock` module lets you freeze time and move it forward by hand, to test
75
+ code that uses the EventMachine `add_timer` and `add_periodic_timer` methods.
76
+
77
+ ```rb
78
+ require "rspec/em"
79
+
80
+ describe "Math" do
81
+ include RSpec::EM::FakeClock
82
+
83
+ before { clock.stub }
84
+ after { clock.reset }
85
+
86
+ it "adds and multiplies" do
87
+ value = 0
88
+ EM.add_timer(1) { value += 3 }
89
+ EM.add_timer(3) { value *= 2 }
90
+
91
+ clock.tick(2)
92
+ value.should == 3
93
+ clock.tick(2)
94
+ value.should == 6
95
+ end
96
+ end
97
+ ```
98
+
99
+
100
+ ## License
101
+
102
+ Copyright (c) 2011-2013 James Coglan
103
+
104
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
105
+ this software and associated documentation files (the "Software"), to deal in
106
+ the Software without restriction, including without limitation the rights to
107
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
108
+ the Software, and to permit persons to whom the Software is furnished to do so,
109
+ subject to the following conditions:
110
+
111
+ The above copyright notice and this permission notice shall be included in all
112
+ copies or substantial portions of the Software.
113
+
114
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
115
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
116
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
117
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
118
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
119
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
120
+
data/lib/rspec/em.rb ADDED
@@ -0,0 +1,2 @@
1
+ require File.expand_path('../eventmachine', __FILE__)
2
+
@@ -0,0 +1,19 @@
1
+ require 'eventmachine'
2
+ require 'rspec/mocks'
3
+ require 'set'
4
+
5
+ module RSpec
6
+ module EventMachine
7
+ root = File.expand_path('../eventmachine', __FILE__)
8
+
9
+ autoload :AsyncSteps, root + '/async_steps'
10
+ autoload :FakeClock, root + '/fake_clock'
11
+
12
+ def self.async_steps(&block)
13
+ AsyncSteps.new(&block)
14
+ end
15
+ end
16
+
17
+ EM = EventMachine
18
+ end
19
+
@@ -0,0 +1,83 @@
1
+ module RSpec::EM
2
+ class AsyncSteps < Module
3
+
4
+ def included(klass)
5
+ klass.__send__(:include, Scheduler)
6
+ end
7
+
8
+ def method_added(method_name)
9
+ async_method_name = "async_#{method_name}"
10
+
11
+ return if instance_methods(false).map { |m| m.to_s }.include?(async_method_name) or
12
+ method_name.to_s =~ /^async_/
13
+
14
+ module_eval <<-RUBY
15
+ alias :#{async_method_name} :#{method_name}
16
+
17
+ def #{method_name}(*args)
18
+ __enqueue__([#{async_method_name.inspect}] + args)
19
+ end
20
+ RUBY
21
+ end
22
+
23
+ module Scheduler
24
+ def __enqueue__(args)
25
+ @__step_queue__ ||= []
26
+ @__step_queue__ << args
27
+ return if @__running_steps__
28
+ @__running_steps__ = true
29
+ EventMachine.next_tick { __run_next_step__ }
30
+ end
31
+
32
+ def __run_next_step__
33
+ step = @__step_queue__.shift
34
+ return EventMachine.stop unless step
35
+
36
+ method_name, args = step.shift, step
37
+ begin
38
+ method(method_name).call(*args) { __run_next_step__ }
39
+ rescue Object => error
40
+ @example.set_exception(error) if @example
41
+ __end_steps__
42
+ end
43
+ end
44
+
45
+ def __end_steps__
46
+ @__step_queue__ = []
47
+ __run_next_step__
48
+ end
49
+
50
+ def verify_mocks_for_rspec
51
+ EventMachine.reactor_running? ? false : super
52
+ rescue Object => error
53
+ @example.set_exception(error) if @example
54
+ end
55
+
56
+ def teardown_mocks_for_rspec
57
+ EventMachine.reactor_running? ? false : super
58
+ rescue Object => error
59
+ @example.set_exception(error) if @example
60
+ end
61
+ end
62
+
63
+ end
64
+ end
65
+
66
+ class RSpec::Core::Example
67
+ hook_method = %w[with_around_hooks with_around_each_hooks].find { |m| instance_method(m) rescue nil }
68
+
69
+ class_eval %Q{
70
+ alias :synchronous_run :#{hook_method}
71
+
72
+ def #{hook_method}(*args, &block)
73
+ if @example_group_instance.is_a?(RSpec::EM::AsyncSteps::Scheduler)
74
+ EventMachine.run { synchronous_run(*args, &block) }
75
+ @example_group_instance.verify_mocks_for_rspec
76
+ @example_group_instance.teardown_mocks_for_rspec
77
+ else
78
+ synchronous_run(*args, &block)
79
+ end
80
+ end
81
+ }
82
+ end
83
+
@@ -0,0 +1,106 @@
1
+ module RSpec::EM
2
+ module FakeClock
3
+
4
+ def clock
5
+ API
6
+ end
7
+
8
+ module API
9
+ STUBBED_METHODS = [:add_timer, :add_periodic_timer, :cancel_timer]
10
+
11
+ def self.stub
12
+ reset
13
+ STUBBED_METHODS.each do |method_name|
14
+ EventMachine.stub(method_name, &FakeClock.method(method_name))
15
+ end
16
+ Time.stub(:now, &FakeClock.method(:now))
17
+ end
18
+
19
+ def self.reset
20
+ FakeClock.reset
21
+ end
22
+
23
+ def self.tick(seconds)
24
+ FakeClock.tick(seconds)
25
+ end
26
+ end
27
+
28
+ class Schedule < SortedSet
29
+ def next_scheduled_at(time)
30
+ find { |timeout| timeout.time <= time }
31
+ end
32
+ end
33
+
34
+ class Timeout
35
+ include Comparable
36
+
37
+ attr_accessor :time
38
+ attr_reader :block, :interval, :repeat
39
+
40
+ def initialize(block, interval, repeat)
41
+ @block = block
42
+ @interval = interval
43
+ @repeat = repeat
44
+ end
45
+
46
+ def <=>(other)
47
+ @time - other.time
48
+ end
49
+ end
50
+
51
+ def self.now
52
+ @call_time
53
+ end
54
+
55
+ def self.reset
56
+ @current_time = Time.now
57
+ @call_time = @current_time
58
+ @schedule = Schedule.new
59
+ end
60
+
61
+ def self.tick(seconds)
62
+ @current_time += seconds
63
+ while timeout = @schedule.next_scheduled_at(@current_time)
64
+ run(timeout)
65
+ end
66
+ @call_time = @current_time
67
+ end
68
+
69
+ def self.run(timeout)
70
+ @call_time = timeout.time
71
+ timeout.block.call
72
+
73
+ if timeout.repeat
74
+ timeout.time += timeout.interval
75
+ @schedule = Schedule.new(@schedule)
76
+ else
77
+ clear_timeout(timeout)
78
+ end
79
+ end
80
+
81
+ def self.timer(block, seconds, repeat)
82
+ timeout = Timeout.new(block, seconds, repeat)
83
+ timeout.time = @call_time + seconds
84
+ @schedule.add(timeout)
85
+ timeout
86
+ end
87
+
88
+ def self.add_timer(seconds, proc = nil, &block)
89
+ timer(block || proc, seconds, false)
90
+ end
91
+
92
+ def self.add_periodic_timer(seconds, proc = nil, &block)
93
+ timer(block || proc, seconds, true)
94
+ end
95
+
96
+ def self.cancel_timer(timeout)
97
+ clear_timeout(timeout)
98
+ end
99
+
100
+ def self.clear_timeout(timeout)
101
+ @schedule.delete(timeout)
102
+ end
103
+
104
+ end
105
+ end
106
+
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rspec-eventmachine
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - James Coglan
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-11-28 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: eventmachine
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 0.12.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 0.12.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '2.0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '2.0'
46
+ description:
47
+ email: jcoglan@gmail.com
48
+ executables: []
49
+ extensions: []
50
+ extra_rdoc_files:
51
+ - README.md
52
+ files:
53
+ - README.md
54
+ - lib/rspec/eventmachine.rb
55
+ - lib/rspec/eventmachine/async_steps.rb
56
+ - lib/rspec/eventmachine/fake_clock.rb
57
+ - lib/rspec/em.rb
58
+ homepage: http://github.com/jcoglan/rspec-eventmachine
59
+ licenses:
60
+ - MIT
61
+ post_install_message:
62
+ rdoc_options:
63
+ - --main
64
+ - README.md
65
+ - --markup
66
+ - markdown
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ! '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ requirements: []
82
+ rubyforge_project:
83
+ rubygems_version: 1.8.23
84
+ signing_key:
85
+ specification_version: 3
86
+ summary: RSpec extensions for testing EventMachine code
87
+ test_files: []