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
data/HISTORY.txt CHANGED
@@ -1,3 +1,15 @@
1
+ === 0.2.0 2010-07-16
2
+
3
+ = New Features
4
+ * added support for cross-stubbing instances [#liangzan]
5
+ * added support for alternative cache stores :memcache & :redis [#ngty]
6
+ * officially support MRI-1.8.7, MRI-1.9.1, JRUBY-1.5.1 & REE-1.8.7 [#ngty]
7
+
8
+ = House-Keeping
9
+
10
+ * dropped ParseTree dependency, use RubyParser instead [#ngty]
11
+ * complete rewrite to have cleaner implementation (hopefully) [#ngty]
12
+
1
13
  === 0.1.4 2010-04-03
2
14
 
3
15
  = Bugfixes
data/README.rdoc CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  == Introduction
4
4
 
5
- Existing mocking/stubbing frameworks support only stubbing in the current process. This is OK most of the time. However, when running cucumber integration test suite in non-webrat mode, these in-process stubbing frameworks simply doesn't help. Eg. I want Time.now to always return a timing that should be a Sunday, how do I do that when running cucumber in selenium, culerity, steam, blah, blah mode? It doesn't seem straight-forward me.
5
+ Existing mocking/stubbing frameworks support only stubbing in the current process. This is OK most of the time. However, when running cucumber integration test suite in another process, these in-process stubbing frameworks simply doesn't help. Eg. I want Time.now to always return a timing that should be a Sunday, how do I do that when running cucumber using selenium, culerity, steam, blah, blah driver? It doesn't seem straight-forward me.
6
6
 
7
7
  (Let's not argue whether stubbing should be encouraged. It is an itch, the poor itch needs to be scratched.)
8
8
 
@@ -14,77 +14,133 @@ It's hosted on gemcutter.org.
14
14
 
15
15
  == Setting Up
16
16
 
17
- #1. If you are using rails, you are in luck:
17
+ === #1. Rails:
18
18
 
19
19
  $ ./script/generate cucumber
20
20
  $ ./script/generate cross_stub
21
21
 
22
- #2. Even if you are using something else, no worries:
22
+ === #2. Others (back to basics):
23
23
 
24
24
  # In the test setup method:
25
- CrossStub.setup :file => <CACHE_FILE_PATH>
25
+ CrossStub.setup :file => <CACHE_FILE>
26
26
 
27
27
  # In the test teardown method:
28
28
  CrossStub.clear
29
29
 
30
30
  # Find an entry point in your target application, eg. in a server, the
31
31
  # point where all request handling starts:
32
- CrossStub.refresh :file => <CACHE_FILE_PATH>
32
+ CrossStub.refresh :file => <CACHE_FILE>
33
+
34
+ For a full list of available cache stores, scroll down to take a look at the 'Cache Stores' section.
33
35
 
34
36
  == Using It
35
37
 
36
- Using cross-stub is simple:
38
+ Cross-stubbing is simple:
39
+
40
+ === #1. Simple returning of nil or non-nil value:
37
41
 
38
- #1. Simple returning of nil or non-nil value:
42
+ ==== #1.1. Class method:
39
43
 
40
44
  class Someone
41
- def self.laugh
42
- 'HaHa'
45
+ def self.say
46
+ 'hello'
43
47
  end
44
48
  end
45
49
 
46
- Someone.xstub(:laugh)
47
- Someone.laugh # yields: nil
50
+ Someone.xstub(:say)
51
+ Someone.say # yields: nil
52
+
53
+ Someone.xstub(:say => 'HELLO')
54
+ Someone.say # yields: 'HELLO'
55
+
56
+ ==== #1.2. Instance method:
57
+
58
+ Someone.xstub(:say, :instance => true)
59
+ Someone.new.say # yields: nil
60
+
61
+ Someone.xstub({:say => 'HELLO'}, :instance => true)
62
+ Someone.new.say # yields: 'HELLO'
48
63
 
49
- Someone.xstub(:laugh, 'HoHo')
50
- Someone.laugh # yields: 'HoHo'
64
+ === #2. If a stubbed method requires argument, pass xstub a proc:
51
65
 
52
- #2. If a stubbed method requires argument, pass :xstub a proc:
66
+ ==== #2.1. Class method:
53
67
 
54
68
  Someone.xstub do
55
- def loves(other)
56
- "I love #{other}"
69
+ def say(something)
70
+ 'saying "%s"' % something
57
71
  end
58
72
  end
59
73
 
60
- Someone.loves('you') # yields: 'I love you'
74
+ Someone.say('HELLO') # yields: 'saying "HELLO"'
75
+
76
+ ==== #2.2. Instance method:
77
+
78
+ Someone.xstub(:instance => true) do
79
+ def say(something)
80
+ 'saying "%s"' % something
81
+ end
82
+ end
83
+
84
+ Someone.new.say('HELLO') # yields: 'saying "HELLO"'
85
+
86
+ IMPORTANT: Since switching from ParseTree to RubyParser, dynamic code analysis is no longer possible. When defining methods in the proc, fanciful coding is strongly discouraged, pls follow the conservative style suggested above.
61
87
 
62
- #3. Something more complicated:
88
+ === #3. Something more complicated:
63
89
 
64
90
  something = 'hello'
65
91
  Someone.xstub do
66
- def do_action(who, action)
67
- %\#{who} #{action} #{something}\
92
+ def say
93
+ 'saying "%s"' % something
68
94
  end
69
95
  end
70
96
 
71
- Someone.do_action('i', 'say') # failure !!
97
+ Someone.say # failure !!
72
98
 
73
99
  The above fails as a result of undefined variable/method 'something', to workaround we can have:
74
100
 
75
- Someone.xstub(:something => 'hello') do
76
- def do_action(who, action)
77
- %\#{who} #{action} #{something}\
101
+ Someone.xstub(:something => 'HELLO') do
102
+ def say
103
+ 'saying "%s"' % something
78
104
  end
79
105
  end
80
106
 
81
- Someone.do_action('i', 'say') # yields: 'i say hello'
107
+ Someone.say # yields: 'saying "HELLO"'
108
+
109
+ == Cache Stores
110
+
111
+ Cache stores are needed to allow stubs to be made available for different processes. The following describes all cache stores available:
112
+
113
+ === #1. File
114
+
115
+ # Setting up (current process)
116
+ CrossStub.setup :file => '<CACHE_FILE>'
117
+
118
+ # Refreshing (other process)
119
+ CrossStub.refresh :file => '<CACHE_FILE>'
120
+
121
+ === #2. Memcache (requires memcache-client gem)
122
+
123
+ # Setting up (current process)
124
+ CrossStub.setup :memcache => 'localhost:11211/<CACHE_ID>'
125
+
126
+ # Refreshing (other process)
127
+ CrossStub.refresh :memcache => 'localhost:11211/<CACHE_ID>'
128
+
129
+ === #3. Redis (requires redis gem)
130
+
131
+ # Setting up (current process)
132
+ CrossStub.setup :redis => 'localhost:6379/<CACHE_ID>'
133
+
134
+ # Refreshing (other process)
135
+ CrossStub.refresh :redis => 'localhost:6379/<CACHE_ID>'
136
+
137
+ Adding new store is super easy (w.r.t testing & actual implementation), let me know if u need more :]
82
138
 
83
139
  == Caveats
84
140
 
85
141
  #1. Cross-stub uses ruby's Marshal class to dump & load the stubs, thus it has the same limitations as Marshal
86
142
 
87
- #2. Cross-stub only supports stubbing of class methods, since it makes no sense to do cross process stubbing of instances
143
+ #2. Having switched from ParseTree to RubyParser, dynamic code analysis is no longer available, the proc taken by xstub no longer supports fanciful coding, pls follow the conservative style suggested in this README.
88
144
 
89
145
  == TODO(s)
90
146
 
@@ -92,10 +148,10 @@ The above fails as a result of undefined variable/method 'something', to workaro
92
148
 
93
149
  == Contacts
94
150
 
95
- Written 2009 by:
151
+ Written since 2009 by:
96
152
 
97
- #1. NgTzeYang, contact ngty77[at]gmail.com or http://github.com/ngty
153
+ #1. NgTzeYang, contact ngty77[at]gmail[dot]com or http://github.com/ngty
98
154
 
99
- #2. WongLiangZan, contact liangzan[at]gmail.com or http://github.com/liangzan
155
+ #2. WongLiangZan, contact liangzan[at]gmail[dot]com or http://github.com/liangzan
100
156
 
101
157
  Released under the MIT license
data/Rakefile CHANGED
@@ -12,9 +12,16 @@ begin
12
12
  gem.authors = ["NgTzeYang"]
13
13
  gem.add_development_dependency "bacon", ">= 0.0.0"
14
14
  gem.add_development_dependency "eventmachine", ">= 0.0.0"
15
- gem.add_dependency "ParseTree", "= 3.0.4"
16
15
  gem.add_dependency "ruby2ruby", "= 1.2.4"
16
+ gem.add_dependency "ruby_parser", "= 2.0.4"
17
+ gem.required_ruby_version = '>= 1.8.7'
18
+ # ##
19
+ # TODO: How do we declare the following optional dependencies ??
20
+ # 1. gem.add_dependency "memcache-client", "= 1.8.5"
21
+ # 2. gem.add_dependency "redis", "= 2.0.3"
22
+ # ##
17
23
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
24
+ #
18
25
  end
19
26
  rescue LoadError
20
27
  puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.4
1
+ 0.2.0
data/cross-stub.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{cross-stub}
8
- s.version = "0.1.4"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["NgTzeYang"]
12
- s.date = %q{2010-04-03}
12
+ s.date = %q{2010-07-16}
13
13
  s.description = %q{}
14
14
  s.email = %q{ngty77@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -26,31 +26,49 @@ Gem::Specification.new do |s|
26
26
  "VERSION",
27
27
  "cross-stub.gemspec",
28
28
  "lib/cross-stub.rb",
29
- "lib/cross-stub/cache_helpers.rb",
30
- "lib/cross-stub/pseudo_class.rb",
31
- "lib/cross-stub/setup_helpers.rb",
32
- "lib/cross-stub/stub_helpers.rb",
29
+ "lib/cross-stub/arguments.rb",
30
+ "lib/cross-stub/arguments/array.rb",
31
+ "lib/cross-stub/arguments/hash.rb",
32
+ "lib/cross-stub/arguments/proc.rb",
33
+ "lib/cross-stub/cache.rb",
34
+ "lib/cross-stub/stores.rb",
35
+ "lib/cross-stub/stores/base.rb",
36
+ "lib/cross-stub/stores/file.rb",
37
+ "lib/cross-stub/stores/memcache.rb",
38
+ "lib/cross-stub/stores/redis.rb",
39
+ "lib/cross-stub/stubber.rb",
33
40
  "rails_generators/cross_stub/cross_stub_generator.rb",
34
41
  "rails_generators/cross_stub/templates/config/initializers/cross-stub.rb",
35
42
  "rails_generators/cross_stub/templates/features/support/cross-stub.rb",
36
43
  "spec/.bacon",
37
- "spec/cross-stub/clearing_stubs_spec.rb",
38
- "spec/cross-stub/creating_stubs_spec.rb",
39
- "spec/cross-stub/stubbing_error_spec.rb",
40
- "spec/helpers.rb",
44
+ "spec/arguments/proc_spec.rb",
45
+ "spec/includes.rb",
46
+ "spec/integration/clearing_instance_stubs_spec.rb",
47
+ "spec/integration/clearing_stubs_spec.rb",
48
+ "spec/integration/creating_instance_stubs_spec.rb",
49
+ "spec/integration/creating_stubs_spec.rb",
50
+ "spec/integration/shared_spec.rb",
51
+ "spec/integration/stubbing_error_spec.rb",
52
+ "spec/service.rb",
41
53
  "spec/spec_helper.rb",
42
54
  "tmp/.dummy"
43
55
  ]
44
56
  s.homepage = %q{http://github.com/ngty/cross-stub}
45
57
  s.rdoc_options = ["--charset=UTF-8"]
46
58
  s.require_paths = ["lib"]
47
- s.rubygems_version = %q{1.3.6}
59
+ s.required_ruby_version = Gem::Requirement.new(">= 1.8.7")
60
+ s.rubygems_version = %q{1.3.7}
48
61
  s.summary = %q{Simple cross process stubbing}
49
62
  s.test_files = [
50
- "spec/helpers.rb",
51
- "spec/cross-stub/stubbing_error_spec.rb",
52
- "spec/cross-stub/clearing_stubs_spec.rb",
53
- "spec/cross-stub/creating_stubs_spec.rb",
63
+ "spec/includes.rb",
64
+ "spec/arguments/proc_spec.rb",
65
+ "spec/service.rb",
66
+ "spec/integration/stubbing_error_spec.rb",
67
+ "spec/integration/creating_instance_stubs_spec.rb",
68
+ "spec/integration/shared_spec.rb",
69
+ "spec/integration/clearing_stubs_spec.rb",
70
+ "spec/integration/creating_stubs_spec.rb",
71
+ "spec/integration/clearing_instance_stubs_spec.rb",
54
72
  "spec/spec_helper.rb"
55
73
  ]
56
74
 
@@ -58,22 +76,22 @@ Gem::Specification.new do |s|
58
76
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
59
77
  s.specification_version = 3
60
78
 
61
- if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
79
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
62
80
  s.add_development_dependency(%q<bacon>, [">= 0.0.0"])
63
81
  s.add_development_dependency(%q<eventmachine>, [">= 0.0.0"])
64
- s.add_runtime_dependency(%q<ParseTree>, ["= 3.0.4"])
65
82
  s.add_runtime_dependency(%q<ruby2ruby>, ["= 1.2.4"])
83
+ s.add_runtime_dependency(%q<ruby_parser>, ["= 2.0.4"])
66
84
  else
67
85
  s.add_dependency(%q<bacon>, [">= 0.0.0"])
68
86
  s.add_dependency(%q<eventmachine>, [">= 0.0.0"])
69
- s.add_dependency(%q<ParseTree>, ["= 3.0.4"])
70
87
  s.add_dependency(%q<ruby2ruby>, ["= 1.2.4"])
88
+ s.add_dependency(%q<ruby_parser>, ["= 2.0.4"])
71
89
  end
72
90
  else
73
91
  s.add_dependency(%q<bacon>, [">= 0.0.0"])
74
92
  s.add_dependency(%q<eventmachine>, [">= 0.0.0"])
75
- s.add_dependency(%q<ParseTree>, ["= 3.0.4"])
76
93
  s.add_dependency(%q<ruby2ruby>, ["= 1.2.4"])
94
+ s.add_dependency(%q<ruby_parser>, ["= 2.0.4"])
77
95
  end
78
96
  end
79
97
 
data/lib/cross-stub.rb CHANGED
@@ -1,47 +1,81 @@
1
1
  require 'rubygems'
2
- require 'parse_tree'
2
+ require 'base64'
3
3
  require 'ruby2ruby'
4
- require 'cross-stub/stub_helpers'
5
- require 'cross-stub/setup_helpers'
6
- require 'cross-stub/cache_helpers'
7
- require 'cross-stub/pseudo_class'
4
+ require 'ruby_parser'
5
+ require 'forwardable'
6
+ require 'cross-stub/cache'
7
+ require 'cross-stub/stubber'
8
+ require 'cross-stub/arguments'
9
+ require 'cross-stub/stores'
8
10
 
9
11
  module CrossStub
10
12
 
11
13
  class Error < Exception ; end
12
14
  class CannotStubInstanceError < Error ; end
15
+ class ModuleCannotBeInstantiatedError < Error ; end
13
16
 
14
17
  class << self
15
18
 
16
- include CacheHelpers
17
- include SetupHelpers
18
- include StubHelpers
19
+ extend Forwardable
20
+ def_delegator :'CrossStub::Cache', :setup
19
21
 
20
- attr_reader :options
21
-
22
- def setup(opts)
23
- @options = opts
24
- setup_for_current_process
22
+ def refresh(opts)
23
+ Cache.refresh(opts)
24
+ [[:previous, :unapply], [:current, :apply]].each do |(mode, method)|
25
+ Cache.get(mode).map do |cache_key, stubs|
26
+ type, thing = stubbable(cache_key)
27
+ Stubber.send(method, type, thing, stubs)
28
+ end
29
+ end
25
30
  end
26
31
 
27
32
  def clear
28
- clear_stubs_for_current_process
33
+ Cache.get.map do |cache_key, stubs|
34
+ type, thing = stubbable(cache_key)
35
+ Stubber.unapply(type, thing, stubs)
36
+ end
37
+ Cache.clear
29
38
  end
30
39
 
31
- def apply(*args, &blk)
32
- apply_stubs_for_current_process(*args, &blk)
40
+ def apply(type, thing, cache_key, args, &block)
41
+ Cache.set(Cache.get.merge(
42
+ cache_key => Stubber.apply(type, thing, Arguments.parse(args, &block))
43
+ ))
33
44
  end
34
45
 
35
- def refresh(opts)
36
- @options = opts
37
- apply_or_unapply_stubs_for_other_process
46
+ def stubbable(str)
47
+ [
48
+ str.end_with?(suffix = '#instance') ? :instance : :class,
49
+ klassify(str.sub(suffix,''))
50
+ ]
51
+ end
52
+
53
+ def klassify(str)
54
+ str.split('::').inject(Object){|klass, const| klass.const_get(const) }
38
55
  end
39
56
 
40
57
  end
41
58
 
42
59
  module ClassMethods
43
- def xstub(*args, &blk)
44
- CrossStub.apply(self, args, &blk)
60
+ def xstub(*args, &block)
61
+ if args[-1].is_a?(::Hash) && args[-1][:instance]
62
+ raise ModuleCannotBeInstantiatedError if self.class == Module
63
+ CrossStub.apply(
64
+ :instance, # stubbing for instance
65
+ self, # the class to action on
66
+ '%s#instance' % self, # cache key (storing of stubbing info for other process)
67
+ args.size>1 ? args[0..-2] : [], # stubbing arguments
68
+ &block # any other more complex stubbing arguments
69
+ )
70
+ else
71
+ CrossStub.apply(
72
+ :class, # stubbing for class/module
73
+ self, # the class to action on
74
+ "#{self}", # cache key (storing of stubbing info for other process)
75
+ args, # stubbing arguments
76
+ &block # any other more complex stubbing arguments
77
+ )
78
+ end
45
79
  end
46
80
  end
47
81
 
@@ -53,6 +87,11 @@ module CrossStub
53
87
 
54
88
  end
55
89
 
56
- Object.send(:extend, CrossStub::ClassMethods)
57
- Object.send(:include, CrossStub::InstanceMethods)
58
- Module.send(:include, CrossStub::ClassMethods)
90
+ Object.class_eval do
91
+ extend CrossStub::ClassMethods
92
+ include CrossStub::InstanceMethods
93
+ end
94
+
95
+ Module.class_eval do
96
+ include CrossStub::ClassMethods
97
+ end
@@ -0,0 +1,21 @@
1
+ require 'cross-stub/arguments/hash'
2
+ require 'cross-stub/arguments/array'
3
+ require 'cross-stub/arguments/proc'
4
+
5
+ module CrossStub
6
+ module Arguments #:nodoc:
7
+ class << self
8
+
9
+ def parse(args, &block)
10
+ (
11
+ case args[0]
12
+ when ::Hash then Hash.parse(args[0])
13
+ when ::Symbol then Array.parse(args)
14
+ else {}
15
+ end
16
+ ).merge(block_given? ? Proc.parse(&block) : {})
17
+ end
18
+
19
+ end
20
+ end
21
+ end