cross-stub 0.1.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/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +85 -0
- data/Rakefile +54 -0
- data/VERSION +1 -0
- data/cross-stub.gemspec +74 -0
- data/lib/cross-stub/cache_helpers.rb +48 -0
- data/lib/cross-stub/pseudo_class.rb +55 -0
- data/lib/cross-stub/setup_helpers.rb +13 -0
- data/lib/cross-stub/stub_helpers.rb +64 -0
- data/lib/cross-stub.rb +57 -0
- data/rails_generators/cross_stub/cross_stub_generator.rb +17 -0
- data/rails_generators/cross_stub/templates/config/initializers/cross-stub.rb +8 -0
- data/rails_generators/cross_stub/templates/features/support/cross-stub.rb +5 -0
- data/spec/.bacon +0 -0
- data/spec/cross-stub/clearing_stubs_spec.rb +98 -0
- data/spec/cross-stub/creating_stubs_spec.rb +98 -0
- data/spec/cross-stub/stubbing_error_spec.rb +26 -0
- data/spec/helpers.rb +96 -0
- data/spec/spec_helper.rb +40 -0
- metadata +109 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2009 NgTzeYang
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
= CROSS-STUB makes cross process stubbing possible !!
|
|
2
|
+
|
|
3
|
+
== Introduction
|
|
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.
|
|
6
|
+
|
|
7
|
+
(Let's not argue whether stubbing should be encouraged. It is an itch, the poor itch needs to be scratched.)
|
|
8
|
+
|
|
9
|
+
== Getting Started
|
|
10
|
+
|
|
11
|
+
It's hosted on gemcutter.org.
|
|
12
|
+
|
|
13
|
+
$ sudo gem install cross-stub
|
|
14
|
+
|
|
15
|
+
== Setting Up
|
|
16
|
+
|
|
17
|
+
If you are using rails, you are in luck:
|
|
18
|
+
|
|
19
|
+
$ ./script/generate cucumber
|
|
20
|
+
$ ./script/generate cross_stub
|
|
21
|
+
|
|
22
|
+
== Using It
|
|
23
|
+
|
|
24
|
+
Using cross-stub is simple:
|
|
25
|
+
|
|
26
|
+
#1. Simple returning of nil or non-nil value:
|
|
27
|
+
|
|
28
|
+
class Someone
|
|
29
|
+
def self.laugh
|
|
30
|
+
'HaHa'
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
Someone.xstub(:laugh)
|
|
35
|
+
Someone.laugh # yields: nil
|
|
36
|
+
|
|
37
|
+
Someone.xstub(:laugh, 'HoHo')
|
|
38
|
+
Someone.laugh # yields: 'HoHo'
|
|
39
|
+
|
|
40
|
+
#2. If a stubbed method requires argument, pass :xstub a proc:
|
|
41
|
+
|
|
42
|
+
Someone.xstub do
|
|
43
|
+
def loves(other)
|
|
44
|
+
"I love #{other}"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
Someone.loves('you') # yields: 'I love you'
|
|
49
|
+
|
|
50
|
+
#3. Something more complicated:
|
|
51
|
+
|
|
52
|
+
something = 'hello'
|
|
53
|
+
Someone.xstub do
|
|
54
|
+
def do_action(who, action)
|
|
55
|
+
%\#{who} #{action} #{something}\
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
Someone.do_action('i', 'say') # failure !!
|
|
60
|
+
|
|
61
|
+
The above fails as a result of undefined variable/method 'something', to workaround we can have:
|
|
62
|
+
|
|
63
|
+
Someone.xstub(:something => 'hello') do
|
|
64
|
+
def do_action(who, action)
|
|
65
|
+
%\#{who} #{action} #{something}\
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
Someone.do_action('i', 'say') # yields: 'i say hello'
|
|
70
|
+
|
|
71
|
+
== Caveats
|
|
72
|
+
|
|
73
|
+
1. Cross-stub uses ruby's Marshal class to dump & load the stubs, thus it has the same limitations as Marshal
|
|
74
|
+
|
|
75
|
+
2. Cross-stub only supports stubbing of class methods, since it makes no sense to do cross process stubbing of instances
|
|
76
|
+
|
|
77
|
+
== Contacts
|
|
78
|
+
|
|
79
|
+
Written 2009 by:
|
|
80
|
+
|
|
81
|
+
1. NgTzeYang, contact ngty77[at]gmail.com or http://github.com/ngty
|
|
82
|
+
|
|
83
|
+
2. WongLiangZan, contact liangzan[at]gmail.com or http://github.com/liangzan
|
|
84
|
+
|
|
85
|
+
Released under the MIT license
|
data/Rakefile
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'rake'
|
|
3
|
+
|
|
4
|
+
begin
|
|
5
|
+
require 'jeweler'
|
|
6
|
+
Jeweler::Tasks.new do |gem|
|
|
7
|
+
gem.name = "cross-stub"
|
|
8
|
+
gem.summary = %Q{Simple cross process stubbing}
|
|
9
|
+
gem.description = %Q{}
|
|
10
|
+
gem.email = "ngty77@gmail.com"
|
|
11
|
+
gem.homepage = "http://github.com/ngty/cross-stub"
|
|
12
|
+
gem.authors = ["NgTzeYang"]
|
|
13
|
+
gem.add_development_dependency "bacon", ">= 0"
|
|
14
|
+
gem.add_dependency "ParseTree", "= 3.0.4"
|
|
15
|
+
gem.add_dependency "ruby2ruby", "= 1.2.4"
|
|
16
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
|
17
|
+
end
|
|
18
|
+
rescue LoadError
|
|
19
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
require 'rake/testtask'
|
|
23
|
+
Rake::TestTask.new(:spec) do |spec|
|
|
24
|
+
spec.libs << 'lib' << 'spec'
|
|
25
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
|
26
|
+
spec.verbose = true
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
begin
|
|
30
|
+
require 'rcov/rcovtask'
|
|
31
|
+
Rcov::RcovTask.new do |spec|
|
|
32
|
+
spec.libs << 'spec'
|
|
33
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
|
34
|
+
spec.verbose = true
|
|
35
|
+
end
|
|
36
|
+
rescue LoadError
|
|
37
|
+
task :rcov do
|
|
38
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
task :spec => :check_dependencies
|
|
43
|
+
|
|
44
|
+
task :default => :spec
|
|
45
|
+
|
|
46
|
+
require 'rake/rdoctask'
|
|
47
|
+
Rake::RDocTask.new do |rdoc|
|
|
48
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
|
49
|
+
|
|
50
|
+
rdoc.rdoc_dir = 'rdoc'
|
|
51
|
+
rdoc.title = "cross-stub #{version}"
|
|
52
|
+
rdoc.rdoc_files.include('README*')
|
|
53
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
54
|
+
end
|
data/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.1.0
|
data/cross-stub.gemspec
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Generated by jeweler
|
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
|
4
|
+
# -*- encoding: utf-8 -*-
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |s|
|
|
7
|
+
s.name = %q{cross-stub}
|
|
8
|
+
s.version = "0.1.0"
|
|
9
|
+
|
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
|
11
|
+
s.authors = ["NgTzeYang"]
|
|
12
|
+
s.date = %q{2009-12-05}
|
|
13
|
+
s.description = %q{}
|
|
14
|
+
s.email = %q{ngty77@gmail.com}
|
|
15
|
+
s.extra_rdoc_files = [
|
|
16
|
+
"LICENSE",
|
|
17
|
+
"README.rdoc"
|
|
18
|
+
]
|
|
19
|
+
s.files = [
|
|
20
|
+
".document",
|
|
21
|
+
".gitignore",
|
|
22
|
+
"LICENSE",
|
|
23
|
+
"README.rdoc",
|
|
24
|
+
"Rakefile",
|
|
25
|
+
"VERSION",
|
|
26
|
+
"cross-stub.gemspec",
|
|
27
|
+
"lib/cross-stub.rb",
|
|
28
|
+
"lib/cross-stub/cache_helpers.rb",
|
|
29
|
+
"lib/cross-stub/pseudo_class.rb",
|
|
30
|
+
"lib/cross-stub/setup_helpers.rb",
|
|
31
|
+
"lib/cross-stub/stub_helpers.rb",
|
|
32
|
+
"rails_generators/cross_stub/cross_stub_generator.rb",
|
|
33
|
+
"rails_generators/cross_stub/templates/config/initializers/cross-stub.rb",
|
|
34
|
+
"rails_generators/cross_stub/templates/features/support/cross-stub.rb",
|
|
35
|
+
"spec/.bacon",
|
|
36
|
+
"spec/cross-stub/clearing_stubs_spec.rb",
|
|
37
|
+
"spec/cross-stub/creating_stubs_spec.rb",
|
|
38
|
+
"spec/cross-stub/stubbing_error_spec.rb",
|
|
39
|
+
"spec/helpers.rb",
|
|
40
|
+
"spec/spec_helper.rb"
|
|
41
|
+
]
|
|
42
|
+
s.homepage = %q{http://github.com/ngty/cross-stub}
|
|
43
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
|
44
|
+
s.require_paths = ["lib"]
|
|
45
|
+
s.rubygems_version = %q{1.3.5}
|
|
46
|
+
s.summary = %q{Simple cross process stubbing}
|
|
47
|
+
s.test_files = [
|
|
48
|
+
"spec/spec_helper.rb",
|
|
49
|
+
"spec/cross-stub/clearing_stubs_spec.rb",
|
|
50
|
+
"spec/cross-stub/stubbing_error_spec.rb",
|
|
51
|
+
"spec/cross-stub/creating_stubs_spec.rb",
|
|
52
|
+
"spec/helpers.rb"
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
if s.respond_to? :specification_version then
|
|
56
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
|
57
|
+
s.specification_version = 3
|
|
58
|
+
|
|
59
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
|
60
|
+
s.add_development_dependency(%q<bacon>, [">= 0"])
|
|
61
|
+
s.add_runtime_dependency(%q<ParseTree>, ["= 3.0.4"])
|
|
62
|
+
s.add_runtime_dependency(%q<ruby2ruby>, ["= 1.2.4"])
|
|
63
|
+
else
|
|
64
|
+
s.add_dependency(%q<bacon>, [">= 0"])
|
|
65
|
+
s.add_dependency(%q<ParseTree>, ["= 3.0.4"])
|
|
66
|
+
s.add_dependency(%q<ruby2ruby>, ["= 1.2.4"])
|
|
67
|
+
end
|
|
68
|
+
else
|
|
69
|
+
s.add_dependency(%q<bacon>, [">= 0"])
|
|
70
|
+
s.add_dependency(%q<ParseTree>, ["= 3.0.4"])
|
|
71
|
+
s.add_dependency(%q<ruby2ruby>, ["= 1.2.4"])
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module CrossStub
|
|
2
|
+
|
|
3
|
+
private
|
|
4
|
+
|
|
5
|
+
module CacheHelpers
|
|
6
|
+
|
|
7
|
+
def setup_cache
|
|
8
|
+
File.open(cache_file, 'w') {|f| Marshal.dump({}, f) }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def update_cache(&blk)
|
|
12
|
+
dump_cache(yield(load_cache))
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def delete_cache
|
|
16
|
+
if File.exists?(cache_file)
|
|
17
|
+
File.exists?(backup_cache_file) ?
|
|
18
|
+
File.delete(cache_file) : File.rename(cache_file, backup_cache_file)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def load_cache
|
|
23
|
+
File.open(cache_file,'r') {|f| Marshal.load(f) }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def load_backup_cache
|
|
27
|
+
cache = {}
|
|
28
|
+
if File.exists?(backup_cache_file)
|
|
29
|
+
cache = File.open(backup_cache_file, 'r') {|f| Marshal.load(f) }
|
|
30
|
+
File.delete(backup_cache_file)
|
|
31
|
+
end
|
|
32
|
+
cache
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def dump_cache(data)
|
|
36
|
+
File.open(cache_file,'w') {|f| Marshal.dump(data, f) }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def backup_cache_file
|
|
40
|
+
%\#{options[:file]}.bak\
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def cache_file
|
|
44
|
+
options[:file]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
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 = klass.is_a?(String) ? Object.const_get(klass) : klass
|
|
14
|
+
@metaclass = (class << @klass ; self ; end)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def id
|
|
18
|
+
@klass.to_s
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def method_code(method)
|
|
22
|
+
@@translator[@metaclass, method]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def replace_method(method, value_or_code)
|
|
26
|
+
old_method_code = method_code(method)
|
|
27
|
+
new_method_code = "#{value_or_code}" =~ /^def / ? value_or_code :
|
|
28
|
+
%\def #{method}; Marshal.load(%|#{Marshal.dump(value_or_code)}|) ; end\
|
|
29
|
+
@klass.instance_eval(new_method_code)
|
|
30
|
+
old_method_code
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def remove_method(method)
|
|
34
|
+
@metaclass.send(:remove_method, method) rescue nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def replace_methods(&blk)
|
|
38
|
+
(tmp = BlankObject.new).__instance_eval__(&blk)
|
|
39
|
+
methods_in_block = tmp.__methods__ - BlankObject.new.__methods__
|
|
40
|
+
original_method_codes = methods_in_block.inject({}) do |memo, method|
|
|
41
|
+
memo.merge(method => method_code(method))
|
|
42
|
+
end
|
|
43
|
+
@klass.instance_eval(&blk)
|
|
44
|
+
original_method_codes
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class BlankObject
|
|
50
|
+
alias_method :__instance_eval__, :instance_eval
|
|
51
|
+
alias_method :__methods__, :methods
|
|
52
|
+
instance_methods.each {|m| undef_method m unless m =~ /^__.*__$/ }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
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.replace_method(method, codes[:before]) : 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
|
+
original_method_code = pk.replace_method(method, value)
|
|
48
|
+
cache[method] ||= {:before => original_method_code}
|
|
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, original_method_code = args
|
|
57
|
+
cache[method] ||= {:before => original_method_code}
|
|
58
|
+
cache[method][:after] = pk.method_code(method)
|
|
59
|
+
cache
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
end
|
|
64
|
+
end
|
data/lib/cross-stub.rb
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'parse_tree'
|
|
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'
|
|
8
|
+
|
|
9
|
+
module CrossStub
|
|
10
|
+
|
|
11
|
+
class Error < Exception ; end
|
|
12
|
+
class CannotStubInstanceError < Error ; end
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
|
|
16
|
+
include CacheHelpers
|
|
17
|
+
include SetupHelpers
|
|
18
|
+
include StubHelpers
|
|
19
|
+
|
|
20
|
+
attr_reader :options
|
|
21
|
+
|
|
22
|
+
def setup(opts)
|
|
23
|
+
@options = opts
|
|
24
|
+
setup_for_current_process
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def clear
|
|
28
|
+
clear_stubs_for_current_process
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def apply(*args, &blk)
|
|
32
|
+
apply_stubs_for_current_process(*args, &blk)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def refresh(opts)
|
|
36
|
+
@options = opts
|
|
37
|
+
apply_or_unapply_stubs_for_other_process
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
module ClassMethods
|
|
43
|
+
def xstub(*args, &blk)
|
|
44
|
+
CrossStub.apply(self, args, &blk)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
module InstanceMethods
|
|
49
|
+
def xstub(*args)
|
|
50
|
+
raise CannotStubInstanceError
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
Object.send(:extend, CrossStub::ClassMethods)
|
|
57
|
+
Object.send(:include, CrossStub::InstanceMethods)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class CrossStubGenerator < Rails::Generator::Base
|
|
2
|
+
|
|
3
|
+
def manifest
|
|
4
|
+
record do |m|
|
|
5
|
+
m.file 'config/initializers/cross-stub.rb', 'config/initializers/cross-stub.rb'
|
|
6
|
+
m.file 'features/support/cross-stub.rb', 'features/support/cross-stub.rb'
|
|
7
|
+
m.gsub_file 'config/environments/cucumber.rb', /\z/, "config.gem 'cross-stub', :version => '>=0.1.0'\n"
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
protected
|
|
12
|
+
|
|
13
|
+
def banner
|
|
14
|
+
"Usage: #{$0} cross_stub"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
end
|
data/spec/.bacon
ADDED
|
File without changes
|
|
@@ -0,0 +1,98 @@
|
|
|
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
|
+
it "should clear hash generated stub and return original value for #{mode} process" do
|
|
12
|
+
original_value = AnyClass.say_world
|
|
13
|
+
AnyClass.xstub(:say_world => 'i say world')
|
|
14
|
+
CrossStub.clear
|
|
15
|
+
@get_value['AnyClass.say_world'].should.equal original_value
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "should clear hash generated stub and raise NoMethodError for #{mode} process" do
|
|
19
|
+
should.raise(NoMethodError) do
|
|
20
|
+
AnyClass.xstub(:say_hello => 'i say hello')
|
|
21
|
+
CrossStub.clear
|
|
22
|
+
@get_value['AnyClass.say_hello']
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "should clear symbol generated stub and return original value for #{mode} process" do
|
|
27
|
+
original_value = AnyClass.say_world
|
|
28
|
+
AnyClass.xstub(:say_world)
|
|
29
|
+
CrossStub.clear
|
|
30
|
+
@get_value['AnyClass.say_world'].should.equal original_value
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "should clear symbol generated stub and raise NoMethodError for #{mode} process" do
|
|
34
|
+
should.raise(NoMethodError) do
|
|
35
|
+
AnyClass.xstub(:say_hello)
|
|
36
|
+
CrossStub.clear
|
|
37
|
+
@get_value['AnyClass.say_hello']
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "should clear block generated stub and return original value for #{mode} process" do
|
|
42
|
+
original_value = AnyClass.say_world
|
|
43
|
+
AnyClass.xstub do
|
|
44
|
+
def say_world ; 'i say world' ; end
|
|
45
|
+
end
|
|
46
|
+
CrossStub.clear
|
|
47
|
+
@get_value['AnyClass.say_world'].should.equal original_value
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it "should clear block generated stub and raise NoMethodError for #{mode} process" do
|
|
51
|
+
should.raise(NoMethodError) do
|
|
52
|
+
AnyClass.xstub do
|
|
53
|
+
def say_hello ; 'i say hello' ; end
|
|
54
|
+
end
|
|
55
|
+
CrossStub.clear
|
|
56
|
+
@get_value['AnyClass.say_hello']
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "should always clear previously generated stub for #{mode} process" do
|
|
61
|
+
original_value = AnyClass.say_world
|
|
62
|
+
|
|
63
|
+
# Stub an existing method
|
|
64
|
+
AnyClass.xstub(:say_world => 'i say world')
|
|
65
|
+
@get_value['AnyClass.say_world']
|
|
66
|
+
|
|
67
|
+
# Clear stubs without refreshing another process
|
|
68
|
+
CrossStub.clear
|
|
69
|
+
CrossStub.setup(:file => $cache_file)
|
|
70
|
+
|
|
71
|
+
# Stub a non-existing method
|
|
72
|
+
AnyClass.xstub(:say_hello => 'i say hello')
|
|
73
|
+
@get_value['AnyClass.say_hello']
|
|
74
|
+
|
|
75
|
+
# Make sure existing method returns to original method
|
|
76
|
+
@get_value['AnyClass.say_world'].should.equal original_value
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "should always clear previously generated stub and raise NoMethodError for #{mode} process" do
|
|
80
|
+
# Stub a non-existing method
|
|
81
|
+
AnyClass.xstub(:say_hello => 'i say hello')
|
|
82
|
+
@get_value['AnyClass.say_hello']
|
|
83
|
+
|
|
84
|
+
# Clear stubs without refreshing another process
|
|
85
|
+
CrossStub.clear
|
|
86
|
+
CrossStub.setup(:file => $cache_file)
|
|
87
|
+
|
|
88
|
+
# Stub an existing method
|
|
89
|
+
AnyClass.xstub(:say_world => 'i say world')
|
|
90
|
+
@get_value['AnyClass.say_world']
|
|
91
|
+
|
|
92
|
+
# Make sure accessing non-existing method throws error
|
|
93
|
+
should.raise(NoMethodError) { @get_value['AnyClass.say_hello'] }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
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
|
+
it "should create with hash argument(s) for #{mode} process" do
|
|
12
|
+
AnyClass.xstub(:say_hello => 'i say hello', :say_world => 'i say world')
|
|
13
|
+
@get_value['AnyClass.say_hello'].should.equal 'i say hello'
|
|
14
|
+
@get_value['AnyClass.say_world'].should.equal 'i say world'
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "should create with symbol argument(s) for #{mode} process" do
|
|
18
|
+
AnyClass.xstub(:say_hello)
|
|
19
|
+
@get_value['AnyClass.say_hello'].should.equal nil
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "should create with block with no argument for #{mode} process" do
|
|
23
|
+
AnyClass.xstub do
|
|
24
|
+
def say_hello ; 'i say hello' ; end
|
|
25
|
+
end
|
|
26
|
+
@get_value['AnyClass.say_hello'].should.equal 'i say hello'
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "should create with symbol & block with no argument for #{mode} process" do
|
|
30
|
+
AnyClass.xstub(:say_hello) do
|
|
31
|
+
def say_world
|
|
32
|
+
'i say world'
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
@get_value['AnyClass.say_hello'].should.equal nil
|
|
36
|
+
@get_value['AnyClass.say_world'].should.equal 'i say world'
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "should create with hash & block with no argument for #{mode} process" do
|
|
40
|
+
AnyClass.xstub(:say_hello => 'i say hello') do
|
|
41
|
+
def say_world
|
|
42
|
+
'i say world'
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
@get_value['AnyClass.say_hello'].should.equal 'i say hello'
|
|
46
|
+
@get_value['AnyClass.say_world'].should.equal 'i say world'
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "should always create the most recent for #{mode} process" do
|
|
50
|
+
found, expected = [], ['i say hello', 'i say something else', 'i say something else again']
|
|
51
|
+
stub_and_get_value = lambda do |value|
|
|
52
|
+
AnyClass.xstub(:say_hello => value)
|
|
53
|
+
@get_value['AnyClass.say_hello']
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
found << stub_and_get_value[expected[0]]
|
|
57
|
+
found << stub_and_get_value[expected[1]]
|
|
58
|
+
|
|
59
|
+
CrossStub.clear
|
|
60
|
+
CrossStub.setup(:file => $cache_file)
|
|
61
|
+
|
|
62
|
+
found << stub_and_get_value[expected[2]]
|
|
63
|
+
found.should.equal expected
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "should create stub with dependency on other stub for #{mode} process" do
|
|
67
|
+
AnyClass.xstub(:something => 'hello') do
|
|
68
|
+
def do_action(who, action)
|
|
69
|
+
%\#{who} #{action} #{something}\
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
@get_value['AnyClass.do_action.i.say'].should.equal 'i say hello'
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# it "should create with block that takes argument(s) for #{mode} process" do
|
|
76
|
+
# # a, b = 1, 2
|
|
77
|
+
# # AnyClass.xstub do |a, b|
|
|
78
|
+
# # def say_hello
|
|
79
|
+
# # "i say #{a+b} hellos"
|
|
80
|
+
# # end
|
|
81
|
+
# # end
|
|
82
|
+
# # AnyClass.say_hello.should.equal 'i say 3 hellos'
|
|
83
|
+
# end
|
|
84
|
+
#
|
|
85
|
+
# it "should create with hash & block that takes argument(s) for #{mode} process" do
|
|
86
|
+
# # a, b = 1, 2
|
|
87
|
+
# # AnyClass.xstub(:say_world => 'i say world') do |a, b|
|
|
88
|
+
# # def say_hello
|
|
89
|
+
# # "i say #{a+b} hellos"
|
|
90
|
+
# # end
|
|
91
|
+
# # end
|
|
92
|
+
# # AnyClass.say_hello.should.equal 'i say 3 hellos'
|
|
93
|
+
# # AnyClass.say_world.should.equal 'i say world'
|
|
94
|
+
# end
|
|
95
|
+
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
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 be raised when stubbing instance' do
|
|
20
|
+
should.raise(CrossStub::CannotStubInstanceError) do
|
|
21
|
+
o = AnyClass.new
|
|
22
|
+
o.xstub(:say_hello => 'i say hello')
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|
data/spec/helpers.rb
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'eventmachine'
|
|
3
|
+
|
|
4
|
+
$cache_file = '/tmp/crossstub.cache'
|
|
5
|
+
|
|
6
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
7
|
+
require 'cross-stub'
|
|
8
|
+
|
|
9
|
+
class AnyClass
|
|
10
|
+
def self.say_world
|
|
11
|
+
'u say world'
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class AnyModule
|
|
16
|
+
def self.say_world
|
|
17
|
+
'u say world'
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
module EchoClient
|
|
22
|
+
|
|
23
|
+
class << self
|
|
24
|
+
|
|
25
|
+
attr_accessor :result
|
|
26
|
+
|
|
27
|
+
def get(klass_and_method)
|
|
28
|
+
address, port = EchoServer::ADDRESS, EchoServer::PORT
|
|
29
|
+
EventMachine::run do
|
|
30
|
+
(EventMachine::connect(address, port, EM)).
|
|
31
|
+
execute(klass_and_method) {|data| self.result = data }
|
|
32
|
+
end
|
|
33
|
+
(self.result == '<NIL>') ? nil : self.result
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
module EM
|
|
41
|
+
def receive_data(data)
|
|
42
|
+
@callback[data]
|
|
43
|
+
EventMachine::stop_event_loop
|
|
44
|
+
end
|
|
45
|
+
def execute(method, &blk)
|
|
46
|
+
@callback = blk
|
|
47
|
+
send_data method
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
module EchoServer
|
|
54
|
+
|
|
55
|
+
ADDRESS, PORT = '127.0.0.1', 8081
|
|
56
|
+
|
|
57
|
+
class << self
|
|
58
|
+
|
|
59
|
+
def pid
|
|
60
|
+
@process.pid
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def start(other_process=false)
|
|
64
|
+
unless other_process
|
|
65
|
+
@process = IO.popen("ruby #{__FILE__}")
|
|
66
|
+
sleep 1
|
|
67
|
+
else
|
|
68
|
+
EventMachine::run { EventMachine::start_server(ADDRESS, PORT, EM) }
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def stop
|
|
73
|
+
Process.kill('SIGHUP', pid)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
module EM
|
|
81
|
+
def receive_data(klass_and_method)
|
|
82
|
+
CrossStub.refresh(:file => $cache_file)
|
|
83
|
+
klass, method, *args = klass_and_method.split('.')
|
|
84
|
+
value =
|
|
85
|
+
if args.empty?
|
|
86
|
+
Object.const_get(klass).send(method) rescue $!
|
|
87
|
+
else
|
|
88
|
+
Object.const_get(klass).send(method, *args) rescue $!
|
|
89
|
+
end
|
|
90
|
+
send_data(value.nil? ? '<NIL>' : value)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
EchoServer.start(true) if ($0 == __FILE__)
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'bacon'
|
|
3
|
+
|
|
4
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
|
5
|
+
require 'helpers'
|
|
6
|
+
|
|
7
|
+
Bacon.summary_on_exit
|
|
8
|
+
|
|
9
|
+
shared 'has standard setup' do
|
|
10
|
+
before do
|
|
11
|
+
CrossStub.setup(:file => $cache_file)
|
|
12
|
+
end
|
|
13
|
+
after do
|
|
14
|
+
CrossStub.clear
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
shared 'has current process setup' do
|
|
19
|
+
before do
|
|
20
|
+
@get_value = lambda do |klass_and_method_and_args|
|
|
21
|
+
klass, method, *args = klass_and_method_and_args.split('.')
|
|
22
|
+
args.empty? ? Object.const_get(klass).send(method) :
|
|
23
|
+
Object.const_get(klass).send(method, *args)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
shared 'has other process setup' do
|
|
29
|
+
before do
|
|
30
|
+
EchoServer.start
|
|
31
|
+
@get_value = lambda do |klass_and_method_and_args|
|
|
32
|
+
(value = EchoClient.get(klass_and_method_and_args)) !~ /^undefined method/ ? value :
|
|
33
|
+
Object.we_just_wanna_trigger_a_no_method_error_with_this_very_long_and_weird_method!
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
after do
|
|
37
|
+
EchoServer.stop
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
metadata
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: cross-stub
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- NgTzeYang
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
|
|
12
|
+
date: 2009-12-05 00:00:00 +08:00
|
|
13
|
+
default_executable:
|
|
14
|
+
dependencies:
|
|
15
|
+
- !ruby/object:Gem::Dependency
|
|
16
|
+
name: bacon
|
|
17
|
+
type: :development
|
|
18
|
+
version_requirement:
|
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
20
|
+
requirements:
|
|
21
|
+
- - ">="
|
|
22
|
+
- !ruby/object:Gem::Version
|
|
23
|
+
version: "0"
|
|
24
|
+
version:
|
|
25
|
+
- !ruby/object:Gem::Dependency
|
|
26
|
+
name: ParseTree
|
|
27
|
+
type: :runtime
|
|
28
|
+
version_requirement:
|
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: 3.0.4
|
|
34
|
+
version:
|
|
35
|
+
- !ruby/object:Gem::Dependency
|
|
36
|
+
name: ruby2ruby
|
|
37
|
+
type: :runtime
|
|
38
|
+
version_requirement:
|
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
40
|
+
requirements:
|
|
41
|
+
- - "="
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
version: 1.2.4
|
|
44
|
+
version:
|
|
45
|
+
description: ""
|
|
46
|
+
email: ngty77@gmail.com
|
|
47
|
+
executables: []
|
|
48
|
+
|
|
49
|
+
extensions: []
|
|
50
|
+
|
|
51
|
+
extra_rdoc_files:
|
|
52
|
+
- LICENSE
|
|
53
|
+
- README.rdoc
|
|
54
|
+
files:
|
|
55
|
+
- .document
|
|
56
|
+
- .gitignore
|
|
57
|
+
- LICENSE
|
|
58
|
+
- README.rdoc
|
|
59
|
+
- Rakefile
|
|
60
|
+
- VERSION
|
|
61
|
+
- cross-stub.gemspec
|
|
62
|
+
- lib/cross-stub.rb
|
|
63
|
+
- lib/cross-stub/cache_helpers.rb
|
|
64
|
+
- lib/cross-stub/pseudo_class.rb
|
|
65
|
+
- lib/cross-stub/setup_helpers.rb
|
|
66
|
+
- lib/cross-stub/stub_helpers.rb
|
|
67
|
+
- rails_generators/cross_stub/cross_stub_generator.rb
|
|
68
|
+
- rails_generators/cross_stub/templates/config/initializers/cross-stub.rb
|
|
69
|
+
- rails_generators/cross_stub/templates/features/support/cross-stub.rb
|
|
70
|
+
- spec/.bacon
|
|
71
|
+
- spec/cross-stub/clearing_stubs_spec.rb
|
|
72
|
+
- spec/cross-stub/creating_stubs_spec.rb
|
|
73
|
+
- spec/cross-stub/stubbing_error_spec.rb
|
|
74
|
+
- spec/helpers.rb
|
|
75
|
+
- spec/spec_helper.rb
|
|
76
|
+
has_rdoc: true
|
|
77
|
+
homepage: http://github.com/ngty/cross-stub
|
|
78
|
+
licenses: []
|
|
79
|
+
|
|
80
|
+
post_install_message:
|
|
81
|
+
rdoc_options:
|
|
82
|
+
- --charset=UTF-8
|
|
83
|
+
require_paths:
|
|
84
|
+
- lib
|
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: "0"
|
|
90
|
+
version:
|
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: "0"
|
|
96
|
+
version:
|
|
97
|
+
requirements: []
|
|
98
|
+
|
|
99
|
+
rubyforge_project:
|
|
100
|
+
rubygems_version: 1.3.5
|
|
101
|
+
signing_key:
|
|
102
|
+
specification_version: 3
|
|
103
|
+
summary: Simple cross process stubbing
|
|
104
|
+
test_files:
|
|
105
|
+
- spec/spec_helper.rb
|
|
106
|
+
- spec/cross-stub/clearing_stubs_spec.rb
|
|
107
|
+
- spec/cross-stub/stubbing_error_spec.rb
|
|
108
|
+
- spec/cross-stub/creating_stubs_spec.rb
|
|
109
|
+
- spec/helpers.rb
|