cross-stub 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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