cross-stub 0.1.4 → 0.2.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.
Files changed (39) hide show
  1. data/HISTORY.txt +12 -0
  2. data/README.rdoc +85 -29
  3. data/Rakefile +8 -1
  4. data/VERSION +1 -1
  5. data/cross-stub.gemspec +37 -19
  6. data/lib/cross-stub.rb +63 -24
  7. data/lib/cross-stub/arguments.rb +21 -0
  8. data/lib/cross-stub/arguments/array.rb +16 -0
  9. data/lib/cross-stub/arguments/hash.rb +17 -0
  10. data/lib/cross-stub/arguments/proc.rb +73 -0
  11. data/lib/cross-stub/cache.rb +36 -0
  12. data/lib/cross-stub/stores.rb +4 -0
  13. data/lib/cross-stub/stores/base.rb +38 -0
  14. data/lib/cross-stub/stores/file.rb +39 -0
  15. data/lib/cross-stub/stores/memcache.rb +40 -0
  16. data/lib/cross-stub/stores/redis.rb +41 -0
  17. data/lib/cross-stub/stubber.rb +132 -0
  18. data/rails_generators/cross_stub/cross_stub_generator.rb +1 -1
  19. data/rails_generators/cross_stub/templates/config/initializers/cross-stub.rb +9 -1
  20. data/rails_generators/cross_stub/templates/features/support/cross-stub.rb +16 -2
  21. data/spec/arguments/proc_spec.rb +689 -0
  22. data/spec/includes.rb +103 -0
  23. data/spec/integration/clearing_instance_stubs_spec.rb +119 -0
  24. data/spec/integration/clearing_stubs_spec.rb +118 -0
  25. data/spec/integration/creating_instance_stubs_spec.rb +91 -0
  26. data/spec/integration/creating_stubs_spec.rb +95 -0
  27. data/spec/integration/shared_spec.rb +35 -0
  28. data/spec/integration/stubbing_error_spec.rb +69 -0
  29. data/spec/service.rb +114 -0
  30. data/spec/spec_helper.rb +1 -41
  31. metadata +58 -26
  32. data/lib/cross-stub/cache_helpers.rb +0 -48
  33. data/lib/cross-stub/pseudo_class.rb +0 -82
  34. data/lib/cross-stub/setup_helpers.rb +0 -13
  35. data/lib/cross-stub/stub_helpers.rb +0 -64
  36. data/spec/cross-stub/clearing_stubs_spec.rb +0 -112
  37. data/spec/cross-stub/creating_stubs_spec.rb +0 -110
  38. data/spec/cross-stub/stubbing_error_spec.rb +0 -38
  39. data/spec/helpers.rb +0 -125
@@ -1,82 +0,0 @@
1
- module CrossStub
2
-
3
- private
4
-
5
- class PseudoClass
6
-
7
- @@translator ||= lambda do |metaclass, method|
8
- @@convertor ||= lambda {|sexp| Ruby2Ruby.new.process(Unifier.new.process(sexp)) }
9
- @@convertor[ParseTree.translate(metaclass, method)] rescue nil
10
- end
11
-
12
- def initialize(klass)
13
- @klass = get_class(klass)
14
- @metaclass = (class << @klass ; self ; end)
15
- end
16
-
17
- def get_class(klass)
18
- if klass.is_a?(String)
19
- klass.split(/::/).inject(Object) { |const_train, const| const_train.const_get(const) }
20
- else
21
- klass
22
- end
23
- end
24
-
25
- def id
26
- @klass.to_s
27
- end
28
-
29
- def method_code(method)
30
- @@translator[@metaclass, method]
31
- end
32
-
33
- def replace_method(method, value_or_code)
34
- status = backup_method(method)
35
- @klass.instance_eval "#{value_or_code}" =~ /^def / ? value_or_code :
36
- %\def #{method}; Marshal.load(%|#{Marshal.dump(value_or_code)}|) ; end\
37
- status
38
- end
39
-
40
- def revert_method(method)
41
- new_name = before_stubbing_method_name(method)
42
- @metaclass.instance_eval("alias_method :#{method}, :#{new_name}") rescue nil
43
- remove_method(new_name)
44
- end
45
-
46
- def backup_method(method)
47
- if @klass.respond_to?(method)
48
- !@klass.respond_to?(new_name = before_stubbing_method_name(method)) &&
49
- @metaclass.instance_eval("alias_method :#{new_name}, :#{method}")
50
- true
51
- else
52
- false
53
- end
54
- end
55
-
56
- def remove_method(method)
57
- @metaclass.send(:remove_method, method) rescue nil
58
- end
59
-
60
- def replace_methods(&blk)
61
- (tmp = BlankObject.new).__instance_eval__(&blk)
62
- methods_in_block = tmp.__methods__ - BlankObject.new.__methods__
63
- is_method_implemented_flags = methods_in_block.inject({}) do |memo, method|
64
- memo.merge(method => backup_method(method))
65
- end
66
- @klass.instance_eval(&blk)
67
- is_method_implemented_flags
68
- end
69
-
70
- def before_stubbing_method_name(method)
71
- :"__#{method}_before_xstubbing"
72
- end
73
-
74
- end
75
-
76
- class BlankObject
77
- alias_method :__instance_eval__, :instance_eval
78
- alias_method :__methods__, :methods
79
- instance_methods.each {|m| undef_method m unless m =~ /^__.*__$/ }
80
- end
81
-
82
- end
@@ -1,13 +0,0 @@
1
- module CrossStub
2
-
3
- private
4
-
5
- module SetupHelpers
6
-
7
- def setup_for_current_process
8
- setup_cache
9
- end
10
-
11
- end
12
-
13
- end
@@ -1,64 +0,0 @@
1
- module CrossStub
2
-
3
- private
4
-
5
- module StubHelpers
6
-
7
- def clear_stubs_for_current_process
8
- if File.exists?(options[:file])
9
- unapply_stubs ; delete_cache
10
- end
11
- end
12
-
13
- def apply_stubs_for_current_process(*args, &blk)
14
- pk, args = PseudoClass.new(args[0]), args[1]
15
- update_cache do |entire_cache|
16
- hash = (args[0].is_a?(Hash) ? args[0] : args.inject({}){|h, k| h.merge(k => nil) })
17
- cache = entire_cache[pk.id] || {}
18
- cache = create_stub_from_hash(pk, cache, hash)
19
- cache = create_stub_from_block(pk, cache, &blk) if block_given?
20
- entire_cache.merge(pk.id => cache)
21
- end
22
- end
23
-
24
- def apply_or_unapply_stubs_for_other_process
25
- lambda {
26
- unapply_stubs(load_backup_cache)
27
- load_cache.each do |klass, hash|
28
- pk = PseudoClass.new(klass)
29
- hash.each {|method, codes| pk.replace_method(method, codes[:after]) }
30
- end
31
- }[] rescue nil
32
- end
33
-
34
- def unapply_stubs(cache=nil)
35
- cache ||= load_cache
36
- cache.each do |klass, hash|
37
- pk = PseudoClass.new(klass)
38
- hash.each do |method, codes|
39
- codes[:before] ? pk.revert_method(method) : pk.remove_method(method)
40
- end
41
- end
42
- end
43
-
44
- def create_stub_from_hash(pk, cache, hash)
45
- hash.inject(cache) do |cache, args|
46
- method, value = args
47
- is_method_implemented = pk.replace_method(method, value)
48
- cache[method] ||= {:before => is_method_implemented}
49
- cache[method][:after] = pk.method_code(method)
50
- cache
51
- end
52
- end
53
-
54
- def create_stub_from_block(pk, cache, &blk)
55
- pk.replace_methods(&blk).inject(cache) do |cache, args|
56
- method, is_method_implemented = args
57
- cache[method] ||= {:before => is_method_implemented}
58
- cache[method][:after] = pk.method_code(method)
59
- cache
60
- end
61
- end
62
-
63
- end
64
- end
@@ -1,112 +0,0 @@
1
- require File.dirname(__FILE__) + '/../spec_helper.rb'
2
-
3
- describe 'Clearing Stubs' do
4
-
5
- behaves_like 'has standard setup'
6
-
7
- %w{current other}.each do |mode|
8
-
9
- behaves_like "has #{mode} process setup"
10
-
11
- %w{AnyClass AnyClass::Inner AnyModule AnyModule::Inner}.each do |klass_or_module|
12
-
13
- before do
14
- @context = @get_context[klass_or_module]
15
- end
16
-
17
- it "should clear hash generated stub and return original value for #{klass_or_module} in #{mode} process" do
18
- original_value = @context.say_world
19
- @context.xstub(:say_world => 'i say world')
20
- CrossStub.clear
21
- @get_value["#{@context}.say_world"].should.equal original_value
22
- end
23
-
24
- it "should clear hash generated stub and raise NoMethodError for #{klass_or_module} in #{mode} process" do
25
- should.raise(NoMethodError) do
26
- @context.xstub(:say_hello => 'i say hello')
27
- CrossStub.clear
28
- @get_value["#{@context}.say_hello"]
29
- end
30
- end
31
-
32
- it "should clear symbol generated stub and return original value for #{klass_or_module} in #{mode} process" do
33
- original_value = @context.say_world
34
- @context.xstub(:say_world)
35
- CrossStub.clear
36
- @get_value["#{@context}.say_world"].should.equal original_value
37
- end
38
-
39
- it "should clear symbol generated stub and raise NoMethodError for #{klass_or_module} in #{mode} process" do
40
- should.raise(NoMethodError) do
41
- @context.xstub(:say_hello)
42
- CrossStub.clear
43
- @get_value["#{@context}.say_hello"]
44
- end
45
- end
46
-
47
- it "should clear block generated stub and return original value for #{klass_or_module} in #{mode} process" do
48
- original_value = @context.say_world
49
- @context.xstub do
50
- def say_world ; 'i say world' ; end
51
- end
52
- CrossStub.clear
53
- @get_value["#{@context}.say_world"].should.equal original_value
54
- end
55
-
56
- it "should clear block generated stub and raise NoMethodError for #{klass_or_module} in #{mode} process" do
57
- should.raise(NoMethodError) do
58
- @context.xstub do
59
- def say_hello ; 'i say hello' ; end
60
- end
61
- CrossStub.clear
62
- @get_value["#{@context}.say_hello"]
63
- end
64
- end
65
-
66
- it "should always clear previously generated stub for #{klass_or_module} in #{mode} process" do
67
- original_value = @context.say_world
68
-
69
- # Stub an existing method
70
- @context.xstub(:say_world => 'i say world')
71
- @get_value["#{@context}.say_world"]
72
-
73
- # Clear stubs without refreshing another process
74
- CrossStub.clear
75
- CrossStub.setup(:file => $cache_file)
76
-
77
- # Stub a non-existing method
78
- @context.xstub(:say_hello => 'i say hello')
79
- @get_value["#{@context}.say_hello"]
80
-
81
- # Make sure existing method returns to original method
82
- @get_value["#{@context}.say_world"].should.equal original_value
83
- end
84
-
85
- it "should always clear previously generated stub and raise NoMethodError for #{klass_or_module} in #{mode} process" do
86
- # Stub a non-existing method
87
- @context.xstub(:say_hello => 'i say hello')
88
- @get_value["#{@context}.say_hello"]
89
-
90
- # Clear stubs without refreshing another process
91
- CrossStub.clear
92
- CrossStub.setup(:file => $cache_file)
93
-
94
- # Stub an existing method
95
- @context.xstub(:say_world => 'i say world')
96
- @get_value["#{@context}.say_world"]
97
-
98
- # Make sure accessing non-existing method throws error
99
- should.raise(NoMethodError) { @get_value["#{@context}.say_hello"] }
100
- end
101
-
102
- it "should clear for method not implemented in ruby and return original value for #{klass_or_module} in #{mode} process" do
103
- Time.xstub(:now => 'abc')
104
- CrossStub.clear
105
- value = nil
106
- should.not.raise(NoMethodError) { value = @get_value['Time.now'] }
107
- value.should.not.equal 'abc'
108
- end
109
- end
110
- end
111
-
112
- end
@@ -1,110 +0,0 @@
1
- require File.dirname(__FILE__) + '/../spec_helper.rb'
2
-
3
- describe 'Creating Stubs' do
4
-
5
- behaves_like 'has standard setup'
6
-
7
- %w{current other}.each do |mode|
8
-
9
- behaves_like "has #{mode} process setup"
10
-
11
- %w{AnyClass AnyClass::Inner AnyModule AnyModule::Inner}.each do |klass_or_module|
12
-
13
- before do
14
- @context = @get_context[klass_or_module]
15
- end
16
-
17
- it "should create with hash argument(s) for #{klass_or_module} class in #{mode} process" do
18
- @context.xstub(:say_hello => 'i say hello', :say_world => 'i say world')
19
- @get_value["#{@context}.say_hello"].should.equal 'i say hello'
20
- @get_value["#{@context}.say_world"].should.equal 'i say world'
21
- end
22
-
23
- it "should create with symbol argument(s) for #{klass_or_module} class in #{mode} process" do
24
- @context.xstub(:say_hello)
25
- @get_value["#{@context}.say_hello"].should.equal nil
26
- end
27
-
28
- it "should create with block with no argument for #{klass_or_module} class in #{mode} process" do
29
- @context.xstub do
30
- def say_hello ; 'i say hello' ; end
31
- end
32
- @get_value["#{@context}.say_hello"].should.equal 'i say hello'
33
- end
34
-
35
- it "should create with symbol & block with no argument for #{klass_or_module} class in #{mode} process" do
36
- @context.xstub(:say_hello) do
37
- def say_world
38
- 'i say world'
39
- end
40
- end
41
- @get_value["#{@context}.say_hello"].should.equal nil
42
- @get_value["#{@context}.say_world"].should.equal 'i say world'
43
- end
44
-
45
- it "should create with hash & block with no argument for #{klass_or_module} class in #{mode} process" do
46
- @context.xstub(:say_hello => 'i say hello') do
47
- def say_world
48
- 'i say world'
49
- end
50
- end
51
- @get_value["#{@context}.say_hello"].should.equal 'i say hello'
52
- @get_value["#{@context}.say_world"].should.equal 'i say world'
53
- end
54
-
55
- it "should always create the most recent for #{klass_or_module} class in #{mode} process" do
56
- found, expected = [], ['i say hello', 'i say something else', 'i say something else again']
57
- stub_and_get_value = lambda do |value|
58
- @context.xstub(:say_hello => value)
59
- @get_value["#{@context}.say_hello"]
60
- end
61
-
62
- found << stub_and_get_value[expected[0]]
63
- found << stub_and_get_value[expected[1]]
64
-
65
- CrossStub.clear
66
- CrossStub.setup(:file => $cache_file)
67
-
68
- found << stub_and_get_value[expected[2]]
69
- found.should.equal expected
70
- end
71
-
72
- it "should create stub with dependency on other stub for #{klass_or_module} class in #{mode} process" do
73
- @context.xstub(:something => 'hello') do
74
- def do_action(who, action)
75
- %\#{who} #{action} #{something}\
76
- end
77
- end
78
- @get_value["#{@context}.do_action.i.say"].should.equal 'i say hello'
79
- end
80
-
81
- it "should create for method not implemented in ruby for #{klass_or_module} class in #{mode} process" do
82
- now = Time.now - 365*60*60*24
83
- Time.xstub(:now => now)
84
- @get_value['Time.now'].should.equal now
85
- end
86
-
87
- # it "should create with block that takes argument(s) for #{mode} process" do
88
- # # a, b = 1, 2
89
- # # AnyClass.xstub do |a, b|
90
- # # def say_hello
91
- # # "i say #{a+b} hellos"
92
- # # end
93
- # # end
94
- # # AnyClass.say_hello.should.equal 'i say 3 hellos'
95
- # end
96
- #
97
- # it "should create with hash & block that takes argument(s) for #{mode} process" do
98
- # # a, b = 1, 2
99
- # # AnyClass.xstub(:say_world => 'i say world') do |a, b|
100
- # # def say_hello
101
- # # "i say #{a+b} hellos"
102
- # # end
103
- # # end
104
- # # AnyClass.say_hello.should.equal 'i say 3 hellos'
105
- # # AnyClass.say_world.should.equal 'i say world'
106
- # end
107
- end
108
- end
109
-
110
- end
@@ -1,38 +0,0 @@
1
- require File.dirname(__FILE__) + '/../spec_helper.rb'
2
-
3
- describe 'Stubbing Error' do
4
-
5
- behaves_like 'has standard setup'
6
-
7
- it 'should not be raised when stubbing module' do
8
- should.not.raise(CrossStub::Error) {
9
- AnyModule.xstub(:say_hello => 'i say hello')
10
- }
11
- end
12
-
13
- it 'should not be raised when stubbing class' do
14
- should.not.raise(CrossStub::Error) {
15
- AnyClass.xstub(:say_hello => 'i say hello')
16
- }
17
- end
18
-
19
- it 'should not be raised when stubbing nested class' do
20
- should.not.raise(CrossStub::Error) {
21
- AnyClass::Inner.xstub(:say_hello => 'i say hello')
22
- }
23
- end
24
-
25
- it 'should not be raised when stubbing nested module' do
26
- should.not.raise(CrossStub::Error) {
27
- AnyModule::Inner.xstub(:say_hello => 'i say hello')
28
- }
29
- end
30
-
31
- it 'should be raised when stubbing instance' do
32
- should.raise(CrossStub::CannotStubInstanceError) do
33
- o = AnyClass.new
34
- o.xstub(:say_hello => 'i say hello')
35
- end
36
- end
37
-
38
- end
data/spec/helpers.rb DELETED
@@ -1,125 +0,0 @@
1
- require 'rubygems'
2
- require 'eventmachine'
3
-
4
- $cache_file = File.join(File.dirname(__FILE__), '..', 'tmp', 'stubbing.cache')
5
- $log_file = File.join(File.dirname(__FILE__), '..', 'tmp', 'echoserver.log')
6
- $sleep_time = 1.5 # may need to increase this depending on ur machine's prowess
7
-
8
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
9
- require 'cross-stub'
10
-
11
- class AnyClass
12
- def self.say_world
13
- 'u say world'
14
- end
15
- class Inner
16
- def self.say_world
17
- 'u say world'
18
- end
19
- end
20
- end
21
-
22
- module AnyModule
23
- def self.say_world
24
- 'u say world'
25
- end
26
- module Inner
27
- def self.say_world
28
- 'u say world'
29
- end
30
- end
31
- end
32
-
33
- module EchoClient
34
-
35
- class << self
36
-
37
- attr_accessor :result
38
-
39
- def get(klass_and_method)
40
- address, port = EchoServer::ADDRESS, EchoServer::PORT
41
- EventMachine::run do
42
- (EventMachine::connect(address, port, EM)).
43
- execute(klass_and_method) {|data| self.result = Marshal.load(data) }
44
- end
45
- self.result
46
- end
47
-
48
- end
49
-
50
- private
51
-
52
- module EM
53
- def receive_data(data)
54
- @callback[data]
55
- EventMachine::stop_event_loop
56
- end
57
- def execute(method, &blk)
58
- @callback = blk
59
- send_data method
60
- end
61
- end
62
-
63
- end
64
-
65
- module EchoServer
66
-
67
- ADDRESS, PORT = '127.0.0.1', 8081
68
-
69
- class << self
70
-
71
- def pid
72
- @process.pid
73
- end
74
-
75
- def start(other_process=false)
76
- unless other_process
77
- @process = IO.popen("ruby #{__FILE__}")
78
- sleep $sleep_time
79
- else
80
- EventMachine::run { EventMachine::start_server(ADDRESS, PORT, EM) }
81
- end
82
- end
83
-
84
- def stop
85
- Process.kill('SIGHUP', pid)
86
- end
87
-
88
- end
89
-
90
- private
91
-
92
- module EM
93
- def receive_data(klass_and_method)
94
- log "(1) EchoServer::EM#receive_data ... receives: #{klass_and_method}"
95
- CrossStub.refresh(:file => $cache_file)
96
- log "(2) EchoServer::EM#receive_data ... completes stubs refresh"
97
- klass, method, *args = klass_and_method.split('.')
98
- konst = klass.split(/::/).inject(Object) { |const_train, const| const_train.const_get(const) }
99
- log "(3) EchoServer::EM#receive_data ... parses arguments to:",
100
- " * klass ... #{klass}",
101
- " * method ... #{method}",
102
- " * args ... #{args.inspect}"
103
- value = args.empty? ? konst.send(method) :
104
- konst.send(method, *args) rescue $!.message
105
- log "(4) EchoServer::EM#receive_data ... returns: #{value.inspect}"
106
- send_data(Marshal.dump(value))
107
- log "(5) EchoServer::EM#receive_data ... end"
108
- end
109
-
110
- def log(*msg)
111
- $logger << [msg, ""].flatten.join("\n")
112
- end
113
- end
114
-
115
- end
116
-
117
- if $0 == __FILE__
118
- begin
119
- require 'logger'
120
- $logger = Logger.new($log_file)
121
- EchoServer.start(true)
122
- ensure
123
- $logger.close
124
- end
125
- end