rspec-isolation 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/.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
|
+
|