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.
- data/HISTORY.txt +12 -0
- data/README.rdoc +85 -29
- data/Rakefile +8 -1
- data/VERSION +1 -1
- data/cross-stub.gemspec +37 -19
- data/lib/cross-stub.rb +63 -24
- data/lib/cross-stub/arguments.rb +21 -0
- data/lib/cross-stub/arguments/array.rb +16 -0
- data/lib/cross-stub/arguments/hash.rb +17 -0
- data/lib/cross-stub/arguments/proc.rb +73 -0
- data/lib/cross-stub/cache.rb +36 -0
- data/lib/cross-stub/stores.rb +4 -0
- data/lib/cross-stub/stores/base.rb +38 -0
- data/lib/cross-stub/stores/file.rb +39 -0
- data/lib/cross-stub/stores/memcache.rb +40 -0
- data/lib/cross-stub/stores/redis.rb +41 -0
- data/lib/cross-stub/stubber.rb +132 -0
- data/rails_generators/cross_stub/cross_stub_generator.rb +1 -1
- data/rails_generators/cross_stub/templates/config/initializers/cross-stub.rb +9 -1
- data/rails_generators/cross_stub/templates/features/support/cross-stub.rb +16 -2
- data/spec/arguments/proc_spec.rb +689 -0
- data/spec/includes.rb +103 -0
- data/spec/integration/clearing_instance_stubs_spec.rb +119 -0
- data/spec/integration/clearing_stubs_spec.rb +118 -0
- data/spec/integration/creating_instance_stubs_spec.rb +91 -0
- data/spec/integration/creating_stubs_spec.rb +95 -0
- data/spec/integration/shared_spec.rb +35 -0
- data/spec/integration/stubbing_error_spec.rb +69 -0
- data/spec/service.rb +114 -0
- data/spec/spec_helper.rb +1 -41
- metadata +58 -26
- data/lib/cross-stub/cache_helpers.rb +0 -48
- data/lib/cross-stub/pseudo_class.rb +0 -82
- data/lib/cross-stub/setup_helpers.rb +0 -13
- data/lib/cross-stub/stub_helpers.rb +0 -64
- data/spec/cross-stub/clearing_stubs_spec.rb +0 -112
- data/spec/cross-stub/creating_stubs_spec.rb +0 -110
- data/spec/cross-stub/stubbing_error_spec.rb +0 -38
- data/spec/helpers.rb +0 -125
@@ -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,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
|