rspec-isolation 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +8 -0
- data/README.mkd +88 -0
- data/Rakefile +2 -0
- data/lib/rspec/isolation.rb +10 -0
- data/lib/rspec/isolation/core_ext.rb +71 -0
- data/lib/rspec/isolation/version.rb +5 -0
- data/rspec-isolation.gemspec +25 -0
- data/spec/rspec_isolation_spec.rb +70 -0
- data/spec/spec_helper.rb +83 -0
- metadata +107 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.mkd
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
Running Your Test Cases in Isolation
|
2
|
+
====================================
|
3
|
+
|
4
|
+
The RSpec examples are generally run in a single ruby process. It dosen't matter until your code introduces some **global variables**, which would **be polluted between examples**. A common solution is to reset them in the `after` hook, but it broken the DRY rule and would bring a lots of extra works. The best way is to **run them in separated sub processes**. This gem would help you to achieve this within a single method call.
|
5
|
+
|
6
|
+
Dependency
|
7
|
+
----------
|
8
|
+
This gem is depended on Rspec 2.0 and above. You should have it installed before using this gem.
|
9
|
+
|
10
|
+
Installation
|
11
|
+
------------
|
12
|
+
|
13
|
+
sudo gem install rspec-isolation
|
14
|
+
|
15
|
+
**Note:** The installation would NOT automatically install the rspec 2.0.
|
16
|
+
|
17
|
+
Usage
|
18
|
+
----------
|
19
|
+
Image a spec file exists:
|
20
|
+
|
21
|
+
$greeting = "Hello, world!"
|
22
|
+
|
23
|
+
describe "Test with global vars" do
|
24
|
+
it "should be replace with my name" do
|
25
|
+
$greeting.gsub!(/\bworld\b/, "Kevin")
|
26
|
+
$greeting.should == "Hello, Kevin!"
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should be replace with my hometown name" do
|
30
|
+
$greeting.gsub!(/\bworld\b/, "Wuhan")
|
31
|
+
$greeting.should == "Hello, Wuhan!"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
The `$greeting` is polluted in the first example and the second example would failed.
|
36
|
+
|
37
|
+
Now we require this gem:
|
38
|
+
|
39
|
+
require 'rspec/isolation'
|
40
|
+
|
41
|
+
Then you can choose one of the following ways to enable the isolation:
|
42
|
+
|
43
|
+
1. Using `iso_it`:
|
44
|
+
|
45
|
+
require 'rspec/isolation'
|
46
|
+
$greeting = "Hello, world!"
|
47
|
+
|
48
|
+
describe "Test with global vars" do
|
49
|
+
iso_it "should be replace with my name" do
|
50
|
+
$greeting.gsub!(/\bworld\b/, "Kevin")
|
51
|
+
$greeting.should == "Hello, Kevin!"
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should be replace with my hometown name" do
|
55
|
+
$greeting.gsub!(/\bworld\b/, "Wuhan")
|
56
|
+
$greeting.should == "Hello, Wuhan!"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
This would run the first example in a sub process.
|
61
|
+
|
62
|
+
2. Using `run_in_isolation` in `before(:each)` hook:
|
63
|
+
|
64
|
+
require 'rspec/isolation'
|
65
|
+
$greeting = "Hello, world!"
|
66
|
+
|
67
|
+
describe "Test with global vars" do
|
68
|
+
before(:each) do
|
69
|
+
run_in_isolation
|
70
|
+
end
|
71
|
+
it "should be replace with my name" do
|
72
|
+
$greeting.gsub!(/\bworld\b/, "Kevin")
|
73
|
+
$greeting.should == "Hello, Kevin!"
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should be replace with my hometown name" do
|
77
|
+
$greeting.gsub!(/\bworld\b/, "Wuhan")
|
78
|
+
$greeting.should == "Hello, Wuhan!"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
This would run all examples in separated sub process. Note: It does not work
|
83
|
+
if you put `run_in_isolation` in a `before(:all)` hook.
|
84
|
+
|
85
|
+
Author
|
86
|
+
------
|
87
|
+
Kevin Fu, corntrace@gmail.com, @corntrace. If you like and use this gem, please feel free to give me any
|
88
|
+
recommandation.
|
data/Rakefile
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
RSpec::Core::ExampleGroup.send :include, RSpec::Isolation
|
2
|
+
RSpec::Core::ExampleGroup.alias_example_to :run_in_isolation, :run_in_isolation => true
|
3
|
+
RSpec::Core::ExampleGroup.alias_example_to :iso_it, :run_in_isolation => true
|
4
|
+
|
5
|
+
RSpec::Core::Example.class_eval do
|
6
|
+
|
7
|
+
delegate_to_metadata :run_in_isolation
|
8
|
+
|
9
|
+
def isolated?
|
10
|
+
!!run_in_isolation
|
11
|
+
end
|
12
|
+
|
13
|
+
def isolated_or_normal
|
14
|
+
if isolated?
|
15
|
+
read, write = IO.pipe
|
16
|
+
pid = fork do
|
17
|
+
read.close
|
18
|
+
begin
|
19
|
+
rest = yield
|
20
|
+
write.puts [Marshal.dump(rest)].pack("m")
|
21
|
+
rescue Exception => e
|
22
|
+
write.puts [Marshal.dump(e)].pack("m")
|
23
|
+
end
|
24
|
+
exit!
|
25
|
+
end
|
26
|
+
write.close
|
27
|
+
result = Marshal.load(read.read.unpack("m").first)
|
28
|
+
Process.wait2(pid)
|
29
|
+
return result
|
30
|
+
else
|
31
|
+
yield
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Redefine the run method
|
36
|
+
def run(example_group_instance, reporter)
|
37
|
+
return if RSpec.wants_to_quit
|
38
|
+
@example_group_instance = example_group_instance
|
39
|
+
@example_group_instance.example = self
|
40
|
+
|
41
|
+
start(reporter)
|
42
|
+
|
43
|
+
begin
|
44
|
+
unless pending
|
45
|
+
with_pending_capture do
|
46
|
+
with_around_hooks do
|
47
|
+
begin
|
48
|
+
run_before_each
|
49
|
+
@in_block = true
|
50
|
+
result = isolated_or_normal { @example_group_instance.instance_eval(&@example_block) }
|
51
|
+
raise result if result.class < Exception
|
52
|
+
result
|
53
|
+
rescue Exception => e
|
54
|
+
set_exception(e)
|
55
|
+
ensure
|
56
|
+
@in_block = false
|
57
|
+
run_after_each
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
rescue Exception => e
|
63
|
+
set_exception(e)
|
64
|
+
ensure
|
65
|
+
@example_group_instance.example = nil
|
66
|
+
assign_auto_description
|
67
|
+
end
|
68
|
+
|
69
|
+
finish(reporter)
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
|
3
|
+
require 'rspec/isolation/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "rspec-isolation"
|
7
|
+
s.version = RSpec::Isolation::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Kevin Fu"]
|
10
|
+
s.email = ["corntrace@gmail.com"]
|
11
|
+
s.homepage = "http://rubygems.org/gems/rspec-isolation"
|
12
|
+
s.summary = "Make rspec examples executed in separated processes."
|
13
|
+
s.description = "Make rspec examples executed in separated processes. \
|
14
|
+
Especially used in framework development."
|
15
|
+
|
16
|
+
s.required_rubygems_version = ">= 1.3.6"
|
17
|
+
|
18
|
+
s.add_development_dependency "bundler", ">= 1.0.0.rc.5"
|
19
|
+
|
20
|
+
s.files = `git ls-files`.split("\n")
|
21
|
+
s.executables = `git ls-files`.split("\n").select{|f| f =~ /^bin/}
|
22
|
+
s.require_path = 'lib'
|
23
|
+
|
24
|
+
s.add_dependency("rspec", ["~> 2.0.0"])
|
25
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe "#isolation" do
|
4
|
+
context "in before each" do
|
5
|
+
it "sets the example isolated" do
|
6
|
+
group = RSpec::Core::ExampleGroup.describe do
|
7
|
+
before(:each) {run_in_isolation}
|
8
|
+
example('example') {}
|
9
|
+
end
|
10
|
+
group.run
|
11
|
+
group.examples.first.run_in_isolation.should be_true
|
12
|
+
end
|
13
|
+
end
|
14
|
+
context "in the example" do
|
15
|
+
it "using iso_it sets the example isolated" do
|
16
|
+
group = RSpec::Core::ExampleGroup.describe do
|
17
|
+
iso_it {}
|
18
|
+
end
|
19
|
+
# group.run
|
20
|
+
group.examples.first.run_in_isolation.should be_true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
context "failure and exception capture" do
|
24
|
+
it "should capture failures" do
|
25
|
+
group = RSpec::Core::ExampleGroup.describe
|
26
|
+
example = group.iso_it('example') { 1.should == 2 }
|
27
|
+
|
28
|
+
group.run
|
29
|
+
example.metadata[:execution_result][:exception_encountered].message.should == "expected: 2,\n got: 1 (using ==)"
|
30
|
+
end
|
31
|
+
it "should capture exceptions" do
|
32
|
+
group = RSpec::Core::ExampleGroup.describe
|
33
|
+
example = group.iso_it('example') { raise "FOO" }
|
34
|
+
|
35
|
+
group.run
|
36
|
+
example.metadata[:execution_result][:exception_encountered].message.should == "FOO"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
context "complicated conditions" do
|
40
|
+
RSpec::Core::ExampleGroup.module_eval do
|
41
|
+
def once_then_raise_error
|
42
|
+
$test_counter ||= 0
|
43
|
+
$test_counter += 1
|
44
|
+
raise "In the same process" if $test_counter > 1
|
45
|
+
return true
|
46
|
+
end
|
47
|
+
end
|
48
|
+
it "should raise error if run in the same process" do
|
49
|
+
group = RSpec::Core::ExampleGroup.describe do
|
50
|
+
$test_counter = 0
|
51
|
+
example {once_then_raise_error}
|
52
|
+
example {once_then_raise_error}
|
53
|
+
end
|
54
|
+
group.run
|
55
|
+
group.examples[0].metadata[:execution_result][:exception_encountered].should be_nil
|
56
|
+
group.examples[1].metadata[:execution_result][:exception_encountered].message.should == "In the same process"
|
57
|
+
end
|
58
|
+
it "should not raise if ran in isolation" do
|
59
|
+
group = RSpec::Core::ExampleGroup.describe do
|
60
|
+
$test_counter = 0
|
61
|
+
iso_it {once_then_raise_error}
|
62
|
+
iso_it {once_then_raise_error}
|
63
|
+
example {once_then_raise_error}
|
64
|
+
end
|
65
|
+
group.run
|
66
|
+
group.examples[0].metadata[:execution_result][:exception_encountered].should be_nil
|
67
|
+
group.examples[0].metadata[:execution_result][:exception_encountered].should be_nil
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'rspec/isolation'
|
2
|
+
|
3
|
+
# Dir['./spec/support/**/*.rb'].map {|f| require f}
|
4
|
+
|
5
|
+
module RSpec
|
6
|
+
module Core
|
7
|
+
module Matchers
|
8
|
+
def fail
|
9
|
+
raise_error(::RSpec::Expectations::ExpectationNotMetError)
|
10
|
+
end
|
11
|
+
|
12
|
+
def fail_with(message)
|
13
|
+
raise_error(::RSpec::Expectations::ExpectationNotMetError, message)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class NullObject
|
20
|
+
def method_missing(method, *args, &block)
|
21
|
+
# ignore
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def sandboxed(&block)
|
26
|
+
begin
|
27
|
+
@orig_config = RSpec.configuration
|
28
|
+
@orig_world = RSpec.world
|
29
|
+
new_config = RSpec::Core::Configuration.new
|
30
|
+
new_config.include(RSpec::Matchers)
|
31
|
+
new_world = RSpec::Core::World.new(new_config)
|
32
|
+
RSpec.instance_variable_set(:@configuration, new_config)
|
33
|
+
RSpec.instance_variable_set(:@world, new_world)
|
34
|
+
object = Object.new
|
35
|
+
object.extend(RSpec::Core::ObjectExtensions)
|
36
|
+
object.extend(RSpec::Core::SharedExampleGroup)
|
37
|
+
|
38
|
+
(class << RSpec::Core::ExampleGroup; self; end).class_eval do
|
39
|
+
alias_method :orig_run, :run
|
40
|
+
def run(reporter=nil)
|
41
|
+
@orig_mock_space = RSpec::Mocks::space
|
42
|
+
RSpec::Mocks::space = RSpec::Mocks::Space.new
|
43
|
+
orig_run(reporter || NullObject.new)
|
44
|
+
ensure
|
45
|
+
RSpec::Mocks::space = @orig_mock_space
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
object.instance_eval(&block)
|
50
|
+
ensure
|
51
|
+
(class << RSpec::Core::ExampleGroup; self; end).class_eval do
|
52
|
+
remove_method :run
|
53
|
+
alias_method :run, :orig_run
|
54
|
+
remove_method :orig_run
|
55
|
+
end
|
56
|
+
|
57
|
+
RSpec.instance_variable_set(:@configuration, @orig_config)
|
58
|
+
RSpec.instance_variable_set(:@world, @orig_world)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def in_editor?
|
63
|
+
ENV.has_key?('TM_MODE') || ENV.has_key?('EMACS') || ENV.has_key?('VIM')
|
64
|
+
end
|
65
|
+
|
66
|
+
RSpec.configure do |c|
|
67
|
+
c.color_enabled = !in_editor?
|
68
|
+
c.filter_run :focused => true
|
69
|
+
c.run_all_when_everything_filtered = true
|
70
|
+
c.filter_run_excluding :ruby => lambda {|version|
|
71
|
+
case version.to_s
|
72
|
+
when "!jruby"
|
73
|
+
RUBY_ENGINE != "jruby"
|
74
|
+
when /^> (.*)/
|
75
|
+
!(RUBY_VERSION.to_s > $1)
|
76
|
+
else
|
77
|
+
!(RUBY_VERSION.to_s =~ /^#{version.to_s}/)
|
78
|
+
end
|
79
|
+
}
|
80
|
+
c.around do |example|
|
81
|
+
sandboxed { example.run }
|
82
|
+
end
|
83
|
+
end
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rspec-isolation
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Kevin Fu
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-10-16 00:00:00 +08:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: bundler
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 1
|
30
|
+
- 0
|
31
|
+
- 0
|
32
|
+
- rc
|
33
|
+
- 5
|
34
|
+
version: 1.0.0.rc.5
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: rspec
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
segments:
|
46
|
+
- 2
|
47
|
+
- 0
|
48
|
+
- 0
|
49
|
+
version: 2.0.0
|
50
|
+
type: :runtime
|
51
|
+
version_requirements: *id002
|
52
|
+
description: Make rspec examples executed in separated processes. Especially used in framework development.
|
53
|
+
email:
|
54
|
+
- corntrace@gmail.com
|
55
|
+
executables: []
|
56
|
+
|
57
|
+
extensions: []
|
58
|
+
|
59
|
+
extra_rdoc_files: []
|
60
|
+
|
61
|
+
files:
|
62
|
+
- .gitignore
|
63
|
+
- Gemfile
|
64
|
+
- README.mkd
|
65
|
+
- Rakefile
|
66
|
+
- lib/rspec/isolation.rb
|
67
|
+
- lib/rspec/isolation/core_ext.rb
|
68
|
+
- lib/rspec/isolation/version.rb
|
69
|
+
- rspec-isolation.gemspec
|
70
|
+
- spec/rspec_isolation_spec.rb
|
71
|
+
- spec/spec_helper.rb
|
72
|
+
has_rdoc: true
|
73
|
+
homepage: http://rubygems.org/gems/rspec-isolation
|
74
|
+
licenses: []
|
75
|
+
|
76
|
+
post_install_message:
|
77
|
+
rdoc_options: []
|
78
|
+
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
segments:
|
87
|
+
- 0
|
88
|
+
version: "0"
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
segments:
|
95
|
+
- 1
|
96
|
+
- 3
|
97
|
+
- 6
|
98
|
+
version: 1.3.6
|
99
|
+
requirements: []
|
100
|
+
|
101
|
+
rubyforge_project:
|
102
|
+
rubygems_version: 1.3.7
|
103
|
+
signing_key:
|
104
|
+
specification_version: 3
|
105
|
+
summary: Make rspec examples executed in separated processes.
|
106
|
+
test_files: []
|
107
|
+
|