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
@@ -0,0 +1,16 @@
1
+ module CrossStub
2
+ module Arguments #:nodoc:
3
+ module Array
4
+ class << self
5
+
6
+ def parse(symbols)
7
+ symbols.inject({}) do |memo, name|
8
+ code = "def #{name} ; nil ; end"
9
+ memo.merge(:"#{name}" => code)
10
+ end
11
+ end
12
+
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ module CrossStub
2
+ module Arguments #:nodoc:
3
+ module Hash
4
+ class << self
5
+
6
+ def parse(hash)
7
+ hash.inject({}) do |memo, (name, val)|
8
+ marshalized = Base64.encode64(Marshal.dump(val)).gsub('|','\|')
9
+ code = "def #{name} ; Marshal.load(Base64.decode64(%|#{marshalized}|)) ; end"
10
+ memo.merge(:"#{name}" => code)
11
+ end
12
+ end
13
+
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,73 @@
1
+ module CrossStub
2
+ module Arguments #:nodoc:
3
+ module Proc
4
+
5
+ class << self
6
+ def parse(&block)
7
+ CodeBlock.new(&block).methods_hash
8
+ end
9
+ end
10
+
11
+ class CodeBlock
12
+
13
+ RUBY_PARSER = RubyParser.new
14
+ RUBY_2_RUBY = Ruby2Ruby.new
15
+
16
+ def initialize(&block)
17
+ @block = block
18
+ @methods_hash = ref_methods.inject({}) do |memo, method|
19
+ memo.merge(:"#{method}" => extract_code(method))
20
+ end
21
+ end
22
+
23
+ attr_reader :methods_hash
24
+
25
+ private
26
+
27
+ def extract_code(method)
28
+ ignore, _, code = source_code.match(code_regexp(method))[1..3]
29
+ remaining = source_code.sub(ignore,'')
30
+ while frag = remaining[/^(.*?\Wend)/m,1]
31
+ begin
32
+ sexp = RUBY_PARSER.parse(code += frag)
33
+ return RUBY_2_RUBY.process(sexp) if sexp.inspect =~ sexp_regexp(method)
34
+ rescue SyntaxError, Racc::ParseError, NoMethodError
35
+ remaining.sub!(frag,'')
36
+ end
37
+ end
38
+ end
39
+
40
+ def ref_methods
41
+ @ref_methods ||= (
42
+ (object = Minimalist.new).__instance_eval__(&@block)
43
+ object.__methods__ - Minimalist.instance_methods
44
+ )
45
+ end
46
+
47
+ def source_code
48
+ @source_code ||= (
49
+ file, line_no = /^#<Proc:0x[0-9A-Fa-f]+@(.+):(\d+).*?>$/.match(@block.inspect)[1..2]
50
+ File.readlines(file)[line_no.to_i.pred .. -1].join
51
+ )
52
+ end
53
+
54
+ def code_regexp(method)
55
+ /^(.*?(do|\{)\s*.*?(def\s*#{method}\W))/m
56
+ end
57
+
58
+ def sexp_regexp(method)
59
+ /^(s\(:defn,\ :#{method},\ s\(:args.*\),\ s\(:scope,\ s\(:block,\ .*\))$/
60
+ end
61
+
62
+ class Minimalist
63
+ orig_verbosity, $VERBOSE = $VERBOSE, nil
64
+ alias_method :__instance_eval__, :instance_eval
65
+ alias_method :__methods__, :methods
66
+ instance_methods.each{|meth| undef_method(meth) if meth.to_s !~ /^__.*?__$/ }
67
+ $VERBOSE = orig_verbosity
68
+ end
69
+
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,36 @@
1
+ module CrossStub
2
+
3
+ class UnsupportedCacheStore < Exception ; end
4
+
5
+ module Cache
6
+ class << self
7
+
8
+ extend Forwardable
9
+ def_delegator :@store, :clear
10
+ def_delegator :@store, :get
11
+ def_delegator :@store, :set
12
+
13
+ def setup(opts)
14
+ init(opts, true)
15
+ end
16
+
17
+ def refresh(opts)
18
+ init(opts, false)
19
+ end
20
+
21
+ private
22
+
23
+ def init(opts, truncate)
24
+ type, arg = opts.to_a[0].map(&:to_s)
25
+ @store =
26
+ begin
27
+ store_name = '%s%s' % [type[0..0].upcase, type[1..-1].downcase]
28
+ Stores.const_get(store_name).new(arg, truncate)
29
+ rescue
30
+ raise UnsupportedCacheStore.new('Store type :%s is not supported !!' % type)
31
+ end
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,4 @@
1
+ require 'cross-stub/stores/base'
2
+ require 'cross-stub/stores/file'
3
+ require 'cross-stub/stores/memcache'
4
+ require 'cross-stub/stores/redis'
@@ -0,0 +1,38 @@
1
+ module CrossStub
2
+
3
+ class UnsupportedStoreGetMode < Exception ; end
4
+
5
+ module Stores
6
+ class Base
7
+
8
+ def initialize(truncate)
9
+ truncate && dump(current, {})
10
+ end
11
+
12
+ def get(mode = :current)
13
+ case mode
14
+ when :current then load(current)
15
+ when :previous
16
+ data = load(previous)
17
+ delete(previous)
18
+ data
19
+ else raise UnsupportedStoreGetMode
20
+ end
21
+ end
22
+
23
+ def set(data, mode = :current)
24
+ case mode
25
+ when :current then dump(current, data)
26
+ when :previous then dump(previous, data)
27
+ else raise UnsupportedStoreGetMode
28
+ end
29
+ end
30
+
31
+ def clear
32
+ set(get(:current), :previous) unless exists?(previous)
33
+ delete(current)
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,39 @@
1
+ module CrossStub
2
+ module Stores
3
+ class File < Base
4
+
5
+ def initialize(file, truncate = true)
6
+ @file = file
7
+ super(truncate)
8
+ end
9
+
10
+ def current
11
+ @file
12
+ end
13
+
14
+ def previous
15
+ "#{@file}.stale"
16
+ end
17
+
18
+ private
19
+
20
+
21
+ def exists?(file)
22
+ ::File.exists?(file)
23
+ end
24
+
25
+ def dump(file, data)
26
+ ::File.open(file,'w') {|f| Marshal.dump(data, f) } rescue nil
27
+ end
28
+
29
+ def load(file)
30
+ ::File.open(file,'r') {|f| Marshal.load(f) } rescue {}
31
+ end
32
+
33
+ def delete(file)
34
+ ::File.delete(file) if exists?(file)
35
+ end
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,40 @@
1
+ module CrossStub
2
+ module Stores
3
+ class Memcache < Base
4
+
5
+ def initialize(connection_and_cache_id, truncate = true)
6
+ require 'memcache'
7
+ connection, @cache_id = connection_and_cache_id.split('/')
8
+ @memcache = MemCache.new(connection)
9
+ super(truncate)
10
+ end
11
+
12
+ def current
13
+ @cache_id
14
+ end
15
+
16
+ def previous
17
+ "#{@cache_id}.stale"
18
+ end
19
+
20
+ private
21
+
22
+ def exists?(cache_id)
23
+ not @memcache[cache_id].nil?
24
+ end
25
+
26
+ def dump(cache_id, data)
27
+ @memcache[cache_id] = data
28
+ end
29
+
30
+ def load(cache_id)
31
+ @memcache[cache_id] || {}
32
+ end
33
+
34
+ def delete(cache_id)
35
+ @memcache.delete(cache_id)
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,41 @@
1
+ module CrossStub
2
+ module Stores
3
+ class Redis < Base
4
+
5
+ def initialize(connection_and_cache_id, truncate = true)
6
+ require 'redis'
7
+ connection, @cache_id = connection_and_cache_id.split('/')
8
+ host, port = connection.split(':')
9
+ @redis = ::Redis.new(:host => host, :port => port.to_i)
10
+ super(truncate)
11
+ end
12
+
13
+ def current
14
+ @cache_id
15
+ end
16
+
17
+ def previous
18
+ "#{@cache_id}.stale"
19
+ end
20
+
21
+ private
22
+
23
+ def exists?(cache_id)
24
+ not @redis[cache_id].nil?
25
+ end
26
+
27
+ def dump(cache_id, data)
28
+ @redis[cache_id] = Marshal.dump(data)
29
+ end
30
+
31
+ def load(cache_id)
32
+ (data = @redis[cache_id] ) ? Marshal.load(data) : {}
33
+ end
34
+
35
+ def delete(cache_id)
36
+ @redis.del(cache_id)
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,132 @@
1
+ module CrossStub
2
+ module Stubber #:nodoc:
3
+
4
+ class << self
5
+
6
+ EXIST, CODE = 0, 1
7
+
8
+ extend Forwardable
9
+ def_delegator :@thing, :m_eval
10
+ def_delegator :@thing, :t_eval
11
+ def_delegator :@thing, :has_method?
12
+
13
+ def apply(type, thing, stubs)
14
+ initialize_vars(type, thing, stubs)
15
+ @stubs.values[0].is_a?(::Hash) ?
16
+ apply_stubbing_only : apply_stubbing_and_return_cacheables
17
+ end
18
+
19
+ def unapply(type, thing, stubs)
20
+ initialize_vars(type, thing, stubs)
21
+ @stubs.each do |method, args|
22
+ args[EXIST] ? unapply_aliasing(method) : remove_method(method)
23
+ unstubbify(method)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def initialize_vars(type, thing, stubs)
30
+ @thing, @stubs = (type == :class ? Klass : Instance).new(thing), stubs
31
+ end
32
+
33
+ def apply_stubbing_and_return_cacheables
34
+ @stubs.inject({}) do |memo, (method, code)|
35
+ args = {EXIST => really_has_method?(method), CODE => code}
36
+ apply_aliasing(method) if args[EXIST]
37
+ t_eval(code)
38
+ stubbify(method)
39
+ memo.merge(method => args)
40
+ end
41
+ end
42
+
43
+ def really_has_method?(method)
44
+ (!stubbified?(method) && has_method?(method)) || has_method?(backup_method(method))
45
+ end
46
+
47
+ def apply_stubbing_only
48
+ @stubs.each do |method, args|
49
+ apply_aliasing(method) if args[EXIST]
50
+ t_eval(args[CODE])
51
+ end
52
+ end
53
+
54
+ def apply_aliasing(method)
55
+ unless has_method?(backup_meth = backup_method(method))
56
+ m_eval("alias_method :#{backup_meth}, :#{method}")
57
+ end
58
+ end
59
+
60
+ def unapply_aliasing(method)
61
+ if has_method?(backup_meth = backup_method(method))
62
+ m_eval("alias_method :#{method}, :#{backup_meth}")
63
+ remove_method(backup_meth)
64
+ end
65
+ end
66
+
67
+ def remove_method(method)
68
+ m_eval("undef_method :#{method}") rescue nil
69
+ end
70
+
71
+ def stubbified?(method)
72
+ has_method?(flag_method(method))
73
+ end
74
+
75
+ def stubbify(method)
76
+ t_eval("def #{flag_method(method)} ; end") rescue nil
77
+ end
78
+
79
+ def unstubbify(method)
80
+ remove_method(flag_method(method))
81
+ end
82
+
83
+ def backup_method(method)
84
+ "__#{method}_before_xstubbing"
85
+ end
86
+
87
+ def flag_method(method)
88
+ "__#{method}_has_been_xstubbed"
89
+ end
90
+
91
+ end
92
+
93
+ class Instance
94
+
95
+ def initialize(thing)
96
+ @thing = thing
97
+ end
98
+
99
+ def m_eval(str)
100
+ @thing.class_eval(str)
101
+ end
102
+
103
+ alias_method :t_eval, :m_eval
104
+
105
+ def has_method?(method)
106
+ @thing.instance_methods.map(&:to_s).include?(method.to_s)
107
+ end
108
+
109
+ end
110
+
111
+ class Klass
112
+
113
+ def initialize(thing)
114
+ @thing = thing
115
+ end
116
+
117
+ def m_eval(str)
118
+ (class << @thing ; self ; end).instance_eval(str)
119
+ end
120
+
121
+ def t_eval(str)
122
+ @thing.instance_eval(str)
123
+ end
124
+
125
+ def has_method?(method)
126
+ @thing.respond_to?(method)
127
+ end
128
+
129
+ end
130
+
131
+ end
132
+ end