rspec-isolation 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
4
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source :gemcutter
2
+
3
+ # Specify your gem's dependencies in rspec-isolation.gemspec
4
+ gemspec
5
+
6
+ group :development do
7
+ gem "ruby-debug19", :require => "ruby-debug"
8
+ end
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,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,10 @@
1
+ module RSpec
2
+ module Isolation
3
+ def run_in_isolation
4
+ example.metadata[:run_in_isolation] = true
5
+ end
6
+ end
7
+ end
8
+
9
+ # $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
10
+ require 'rspec/isolation/core_ext'
@@ -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,5 @@
1
+ module RSpec
2
+ module Isolation
3
+ VERSION = "0.1.0"
4
+ end
5
+ 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
@@ -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
+