two-step 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.
- checksums.yaml +7 -0
- data/doc/README +116 -0
- data/doc/Specifications +17 -0
- data/lib/two-step.rb +3 -0
- data/lib/two-step/duckpunch/example-group.rb +27 -0
- data/lib/two-step/duckpunch/example.rb +5 -0
- data/lib/two-step/duckpunch/object-extensions.rb +8 -0
- data/lib/two-step/stepwise.rb +219 -0
- data/spec/example_group_spec.rb +164 -0
- data/spec_help/file-sandbox.rb +164 -0
- data/spec_help/gem_test_suite.rb +17 -0
- data/spec_help/rspec-sandbox.rb +46 -0
- data/spec_help/spec_helper.rb +3 -0
- data/spec_help/ungemmer.rb +36 -0
- metadata +119 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: bbcd87fc9e0ece6a20bedfe6664901250297a175
|
|
4
|
+
data.tar.gz: 2beecdace7f917e4773d529b0e96377c6d9ef938
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 03775343f6f5fb263872e1cfcc2ae48e008e7693f0e3e4373eec8b41827ad4018bd51563a0adcb0cb90e09689aa491f8c877c8fa0259e39ae9d404be008ed3fa
|
|
7
|
+
data.tar.gz: cba2e257f83704719a484584f257e64df337dd2585aa93dcbbdbb3c62cfc5556ce699176db2950c6a68d3dbc183e7d02fc408cb52fa82aeda198542d80041b0c
|
data/doc/README
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Two Step
|
|
2
|
+
## ( or: why would I want to relearn how to write specs? )
|
|
3
|
+
|
|
4
|
+
Two-Step allows you to chain examples into a series of steps that run
|
|
5
|
+
in sequence and which stop when a step fails. It's often incredibly
|
|
6
|
+
useful to be able to aseemble a series of tests that should all pass,
|
|
7
|
+
but where completely isolating them is less than sensible.
|
|
8
|
+
|
|
9
|
+
( Two Step is the RSpec 2 compatible version of RSpec Steps - which can be
|
|
10
|
+
found here: https://github.com/LRDesign/rspec-steps )
|
|
11
|
+
|
|
12
|
+
One excellent example is web site integration tests. With RSpec steps you can
|
|
13
|
+
do:
|
|
14
|
+
|
|
15
|
+
steps "Login and change password" do
|
|
16
|
+
it "should show the login form" do
|
|
17
|
+
visit root
|
|
18
|
+
page.should have_text "Login"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "should successfully log in" do
|
|
22
|
+
fill_in :name, "Johnny User"
|
|
23
|
+
click "Login"
|
|
24
|
+
page.should have_text "Welcome, Johnny!"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "should load the password change form" do
|
|
28
|
+
click "My Settings"
|
|
29
|
+
click "Update Password"
|
|
30
|
+
page.should have_selector("form#update_password")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "should change the user's password successfully" do
|
|
34
|
+
fill_in :password, "foobar"
|
|
35
|
+
fill_in :password_confirmation, "foobar"
|
|
36
|
+
click "Change Password"
|
|
37
|
+
page.should have_text "Password changed successfully!"
|
|
38
|
+
User.find_by_name("Johnny User").valid_password?("foobar").should be_true
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
The examples above will be run in order. State is preserved between examples
|
|
44
|
+
inside a "steps" block: any DB transactions will not roll back until the entire
|
|
45
|
+
sequence has been complete.
|
|
46
|
+
|
|
47
|
+
If any example inside the "steps" block fails, all remaining steps will be marked
|
|
48
|
+
pending and therefore skipped.
|
|
49
|
+
|
|
50
|
+
## Rationale
|
|
51
|
+
|
|
52
|
+
RSpec's philosophy is that all examples should be completely independent. This
|
|
53
|
+
is a great philosophy for most purposes, and we recommend you stick to it in
|
|
54
|
+
almost all cases. BUT, that complete separation of examples really sucks when
|
|
55
|
+
you're trying to write long stories involving many requests. You are usually
|
|
56
|
+
stuck with three choices:
|
|
57
|
+
|
|
58
|
+
1. Write a sequence of examples, each of which repeats the behavior of all previous examples. Downside: horrendously inefficient.
|
|
59
|
+
2. Write a single huge example which performs the entire story. Downside: only one description, no independent reporting of the steps of the story.
|
|
60
|
+
3. Use Cucumber. Downside: We agree totally with this guy: http://bit.ly/dmXqnY
|
|
61
|
+
|
|
62
|
+
Two-Step intentionally breaks RSpec's "independent" philosophy to let us get the
|
|
63
|
+
only thing we really want from Cucumber - the ability to execute some examples in sequence,
|
|
64
|
+
and skip subsequent steps after a failure.
|
|
65
|
+
|
|
66
|
+
## Caveats and cautions
|
|
67
|
+
|
|
68
|
+
Don't call "describe" inside of "steps". The behavior is undefined and probably bad. It's
|
|
69
|
+
hard to imagine what this should actually mean in any case. Future versions of rspec-steps
|
|
70
|
+
will consider this an error.
|
|
71
|
+
|
|
72
|
+
Any call to "before" inside a steps block is treated like before(:all) and is run only
|
|
73
|
+
once before the first step. Or perhaps more accurately, any call to before() is treated
|
|
74
|
+
like before(:each) ... but for these purposes the entire steps block is treated like a
|
|
75
|
+
single example.
|
|
76
|
+
|
|
77
|
+
## Advanced stuff: shared steps
|
|
78
|
+
|
|
79
|
+
If you have (for example) two user stories that share the same first N steps but then
|
|
80
|
+
diverge, you can DRY your code out with shared_steps blocks, like so:
|
|
81
|
+
|
|
82
|
+
shared_steps "For a logged-in user" do
|
|
83
|
+
it "should have login form"
|
|
84
|
+
visit root
|
|
85
|
+
page.should have_selector "form#login"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it "should log the user in" do
|
|
89
|
+
fill_in :name, "Johnny User"
|
|
90
|
+
page.should have_text "Welcome, Johnny!"
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
steps "updating password" do
|
|
95
|
+
perform_steps "For a logged-in user"
|
|
96
|
+
|
|
97
|
+
it "should update the password" do
|
|
98
|
+
...
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
steps "uploading a profile picture" do
|
|
103
|
+
perform_steps "For a logged-in user"
|
|
104
|
+
|
|
105
|
+
it "should upload a picture" do
|
|
106
|
+
...
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
## Versions and Dependencies
|
|
111
|
+
|
|
112
|
+
The goal (sadly unfulfilled) is to try to be compatible with as many versions
|
|
113
|
+
of RSpec 2.x as possible. Frankly, Two-Step is more than a little bit of a
|
|
114
|
+
hack, and intrudes wholesale on RSpec's private interfaces. We make good use of
|
|
115
|
+
Travis to check compatibility, however. You can check what versions of RSpec
|
|
116
|
+
and Ruby Two-Step works with here: https://travis-ci.org/LRDesign/two-step
|
data/doc/Specifications
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
RSpec::Core::ExampleGroup defined as stepwise
|
|
3
|
+
::steps
|
|
4
|
+
should create an ExampleGroup that includes RSpec::Stepwise
|
|
5
|
+
with Stepwise included
|
|
6
|
+
should retain instance variables between steps
|
|
7
|
+
should mark later examples as pending if one fails
|
|
8
|
+
should allow nested steps
|
|
9
|
+
should not allow nested normal contexts (PENDING: A correct approach - in the meantime, this behavior is undefined)
|
|
10
|
+
|
|
11
|
+
Pending:
|
|
12
|
+
RSpec::Core::ExampleGroup defined as stepwise with Stepwise included should not allow nested normal contexts
|
|
13
|
+
# A correct approach - in the meantime, this behavior is undefined
|
|
14
|
+
# ./spec/example_group.rb:63
|
|
15
|
+
|
|
16
|
+
Finished in 0.10849 seconds
|
|
17
|
+
5 examples, 0 failures, 1 pending
|
data/lib/two-step.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'rspec/core'
|
|
2
|
+
require 'two-step/stepwise'
|
|
3
|
+
|
|
4
|
+
module TwoStep
|
|
5
|
+
module DSL
|
|
6
|
+
def steps(*args, &block)
|
|
7
|
+
options =
|
|
8
|
+
if args.last.is_a?(Hash)
|
|
9
|
+
args.pop
|
|
10
|
+
else
|
|
11
|
+
{}
|
|
12
|
+
end
|
|
13
|
+
options[:stepwise] = true
|
|
14
|
+
options[:caller] ||= caller
|
|
15
|
+
args.push(options)
|
|
16
|
+
|
|
17
|
+
describe(*args, &block)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
RSpec::Core::ExampleGroup.extend TwoStep::DSL
|
|
23
|
+
|
|
24
|
+
extend TwoStep::DSL
|
|
25
|
+
Module::send(:include, TwoStep::DSL)
|
|
26
|
+
|
|
27
|
+
RSpec::configuration.include(RSpecStepwise, :stepwise => true)
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
module RSpecStepwise
|
|
2
|
+
class ApatheticReporter < ::RSpec::Core::Reporter
|
|
3
|
+
def notify(*args)
|
|
4
|
+
#noop
|
|
5
|
+
end
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class WholeListExample < RSpec::Core::Example
|
|
9
|
+
def initialize(example_group_class, descriptions, metadata)
|
|
10
|
+
super
|
|
11
|
+
@reporter = ApatheticReporter.new
|
|
12
|
+
build_example_block
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def start(reporter)
|
|
16
|
+
super(@reporter)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def finish(reporter)
|
|
20
|
+
super(@reporter)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def build_example_block
|
|
24
|
+
#variables of concern: reporter, instance
|
|
25
|
+
@example_block = proc do
|
|
26
|
+
begin
|
|
27
|
+
self.class.filtered_examples.inject(true) do |success, example|
|
|
28
|
+
break if RSpec.wants_to_quit
|
|
29
|
+
example.extend StepExample
|
|
30
|
+
unless success
|
|
31
|
+
example.metadata[:pending] = true
|
|
32
|
+
example.metadata[:execution_result][:pending_message] = "Previous step failed"
|
|
33
|
+
end
|
|
34
|
+
succeeded = with_indelible_ivars do
|
|
35
|
+
example.run(self, reporter)
|
|
36
|
+
end
|
|
37
|
+
RSpec.wants_to_quit = true if self.class.fail_fast? && !succeeded
|
|
38
|
+
success && succeeded
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
module StepExample
|
|
46
|
+
def run_before_each
|
|
47
|
+
@example_group_class.run_before_step(self)
|
|
48
|
+
rescue Object => ex
|
|
49
|
+
puts "\n#{__FILE__}:#{__LINE__} => #{[ex, ex.backtrace].pretty_inspect}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def run_after_each
|
|
53
|
+
@example_group_class.run_after_step(self)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def with_around_hooks
|
|
57
|
+
yield
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
module ClassMethods
|
|
62
|
+
#This is hacky and needs a more general solution
|
|
63
|
+
#Something like cloning the current conf and having RSpec::Stepwise::config ?
|
|
64
|
+
def suspend_transactional_fixtures
|
|
65
|
+
if self.respond_to? :use_transactional_fixtures
|
|
66
|
+
begin
|
|
67
|
+
old_val = self.use_transactional_fixtures
|
|
68
|
+
self.use_transactional_fixtures = false
|
|
69
|
+
|
|
70
|
+
yield
|
|
71
|
+
ensure
|
|
72
|
+
self.use_transactional_fixtures = old_val
|
|
73
|
+
end
|
|
74
|
+
else
|
|
75
|
+
yield
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def build_before_hook(options, &block)
|
|
80
|
+
if defined? RSpec::Core::Hooks::BeforeHookExtension
|
|
81
|
+
block.extend(RSpec::Core::Hooks::BeforeHookExtension).with(options)
|
|
82
|
+
else
|
|
83
|
+
RSpec::Core::Hooks::BeforeHook.new(block, options)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def build_after_hook(options, &block)
|
|
88
|
+
if defined? RSpec::Core::Hooks::AfterHookExtension
|
|
89
|
+
block.extend(RSpec::Core::Hooks::AfterHookExtension).with(options)
|
|
90
|
+
else
|
|
91
|
+
RSpec::Core::Hooks::AfterHook.new(block, options)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def before(*args, &block)
|
|
97
|
+
if args.first == :step
|
|
98
|
+
args.shift
|
|
99
|
+
options = build_metadata_hash_from(args)
|
|
100
|
+
return ((hooks[:before][:step] ||= []) << build_before_hook(options, &block))
|
|
101
|
+
end
|
|
102
|
+
if args.first == :each
|
|
103
|
+
puts "before blocks declared for steps are always treated as :all scope"
|
|
104
|
+
end
|
|
105
|
+
super
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def after(*args, &block)
|
|
109
|
+
if args.first == :step
|
|
110
|
+
args.shift
|
|
111
|
+
options = build_metadata_hash_from(args)
|
|
112
|
+
hooks[:after][:step] ||= []
|
|
113
|
+
return (hooks[:after][:step].unshift build_after_hook(options, &block))
|
|
114
|
+
end
|
|
115
|
+
if args.first == :each
|
|
116
|
+
puts "after blocks declared for steps are always treated as :all scope"
|
|
117
|
+
end
|
|
118
|
+
super
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def around(*args, &block)
|
|
122
|
+
if args.first == :each
|
|
123
|
+
puts "around :each blocks declared for steps are treated as :all scope"
|
|
124
|
+
end
|
|
125
|
+
super
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def example_synonym(named, desc=nil, *args, &block)
|
|
129
|
+
unless desc.nil?
|
|
130
|
+
desc = [named, desc].join(" ")
|
|
131
|
+
end
|
|
132
|
+
it(desc, *args, &block)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def when(*args, &block); example_synonym("when", *args, &block); end
|
|
136
|
+
def then(*args, &block); example_synonym("then", *args, &block); end
|
|
137
|
+
def next(*args, &block); example_synonym("next", *args, &block); end
|
|
138
|
+
def step(*args, &block); example_synonym("step", *args, &block); end
|
|
139
|
+
|
|
140
|
+
def run_step(example, hook, &sorting)
|
|
141
|
+
groups = if respond_to?(:parent_groups)
|
|
142
|
+
parent_groups
|
|
143
|
+
else
|
|
144
|
+
ancestors
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
if block_given?
|
|
148
|
+
groups = yield groups
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
RSpec::Core::Hooks::HookCollection.new(groups.map {|a| a.hooks[hook][:step]}.flatten.compact).for(example).run
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def run_before_step(example)
|
|
155
|
+
run_step(example, :before)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def run_after_step(example)
|
|
159
|
+
run_step(example, :after) do |groups|
|
|
160
|
+
groups.reverse
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def perform_steps(name, *args, &customization_block)
|
|
165
|
+
shared_block = nil
|
|
166
|
+
if world.respond_to? :shared_example_groups
|
|
167
|
+
shared_block = world.shared_example_groups[name]
|
|
168
|
+
else
|
|
169
|
+
shared_block = shared_example_groups[name]
|
|
170
|
+
end
|
|
171
|
+
raise "Could not find shared example group named #{name.inspect}" unless shared_block
|
|
172
|
+
|
|
173
|
+
module_eval_with_args(*args, &shared_block)
|
|
174
|
+
module_eval(&customization_block) if customization_block
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def run_examples(reporter)
|
|
178
|
+
whole_list_example = WholeListExample.new(self, "step list", {})
|
|
179
|
+
|
|
180
|
+
instance = new
|
|
181
|
+
set_ivars(instance, before_all_ivars)
|
|
182
|
+
instance.example = whole_list_example
|
|
183
|
+
instance.reporter = reporter
|
|
184
|
+
|
|
185
|
+
result = suspend_transactional_fixtures do
|
|
186
|
+
whole_list_example.run(instance, reporter)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
unless whole_list_example.exception.nil?
|
|
190
|
+
RSpec.wants_to_quit = true if fail_fast?
|
|
191
|
+
fail_filtered_examples(whole_list_example.exception, reporter)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
result
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
attr_accessor :reporter
|
|
199
|
+
|
|
200
|
+
def with_indelible_ivars
|
|
201
|
+
old_value, @ivars_indelible = @ivars_indelible, true
|
|
202
|
+
result = yield
|
|
203
|
+
@ivars_indelible = old_value
|
|
204
|
+
result
|
|
205
|
+
rescue Object
|
|
206
|
+
@ivars_indelible = old_value
|
|
207
|
+
raise
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def instance_variable_set(name, value)
|
|
211
|
+
if !@ivars_indelible
|
|
212
|
+
super
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def self.included(base)
|
|
217
|
+
base.extend(ClassMethods)
|
|
218
|
+
end
|
|
219
|
+
end
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
require 'two-step'
|
|
2
|
+
require 'rspec-sandbox'
|
|
3
|
+
|
|
4
|
+
describe RSpec::Core::ExampleGroup do
|
|
5
|
+
describe "::steps" do
|
|
6
|
+
it "should create an ExampleGroup that includes RSpec::Stepwise" do
|
|
7
|
+
group = nil
|
|
8
|
+
sandboxed do
|
|
9
|
+
group = RSpec.steps "Test Steps" do
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
(class << group; self; end).included_modules.should include(RSpecStepwise::ClassMethods)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
describe "with Stepwise included" do
|
|
17
|
+
it "should retain instance variables between steps" do
|
|
18
|
+
group = nil
|
|
19
|
+
sandboxed do
|
|
20
|
+
group = RSpec.steps "Test Steps" do
|
|
21
|
+
it("sets @a"){ @a = 1 }
|
|
22
|
+
it("reads @a"){ @a.should == 1}
|
|
23
|
+
end
|
|
24
|
+
group.run
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
group.examples.each do |example|
|
|
28
|
+
example.metadata[:execution_result][:status].should == 'passed'
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "should work with shared_steps/perform steps" do
|
|
33
|
+
group = nil
|
|
34
|
+
sandboxed do
|
|
35
|
+
group = RSpec.steps "Test Steps" do
|
|
36
|
+
shared_steps "add one" do
|
|
37
|
+
it("adds one to @a"){ @a += 1 }
|
|
38
|
+
end
|
|
39
|
+
it("sets @a"){ @a = 1 }
|
|
40
|
+
perform_steps "add one"
|
|
41
|
+
perform_steps "add one"
|
|
42
|
+
perform_steps "add one"
|
|
43
|
+
it("reads @a"){ @a.should == 4 }
|
|
44
|
+
end
|
|
45
|
+
group.run
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
group.examples.each do |example|
|
|
49
|
+
example.metadata[:execution_result][:status].should == 'passed'
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "should run each_step hooks" do
|
|
54
|
+
group = nil
|
|
55
|
+
afters = []
|
|
56
|
+
befores = []
|
|
57
|
+
|
|
58
|
+
sandboxed do
|
|
59
|
+
group = RSpec.steps "Test Each Step" do
|
|
60
|
+
before :each do
|
|
61
|
+
befores << :each
|
|
62
|
+
end
|
|
63
|
+
after :each do
|
|
64
|
+
afters << :each
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
before :all do
|
|
68
|
+
befores << :all
|
|
69
|
+
end
|
|
70
|
+
after :all do
|
|
71
|
+
afters << :all
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
before :step do
|
|
75
|
+
befores << :step
|
|
76
|
+
end
|
|
77
|
+
after :step do
|
|
78
|
+
afters << :step
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "should 1" do
|
|
82
|
+
1.should == 1
|
|
83
|
+
end
|
|
84
|
+
it "should 2" do
|
|
85
|
+
2.should == 2
|
|
86
|
+
end
|
|
87
|
+
it "should 3" do
|
|
88
|
+
3.should == 3
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
group.run
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
befores.find_all{|item| item == :all}.length.should == 1
|
|
95
|
+
befores.find_all{|item| item == :each}.length.should == 1
|
|
96
|
+
befores.find_all{|item| item == :step}.length.should == 3
|
|
97
|
+
afters.find_all{|item| item == :all}.length.should == 1
|
|
98
|
+
afters.find_all{|item| item == :each}.length.should == 1
|
|
99
|
+
afters.find_all{|item| item == :step}.length.should == 3
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it "should mark later examples as failed if a before hook fails" do
|
|
103
|
+
group = nil
|
|
104
|
+
exception = Exception.new "Testing Error"
|
|
105
|
+
|
|
106
|
+
sandboxed do
|
|
107
|
+
group = RSpec.steps "Test Steps" do
|
|
108
|
+
before { raise exception }
|
|
109
|
+
it { 1.should == 1 }
|
|
110
|
+
it { 1.should == 1 }
|
|
111
|
+
end
|
|
112
|
+
group.run
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
group.examples.each do |example|
|
|
116
|
+
example.metadata[:execution_result][:status].should == 'failed'
|
|
117
|
+
example.metadata[:execution_result][:exception].should == exception
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "should mark later examples as pending if one fails" do
|
|
122
|
+
group = nil
|
|
123
|
+
sandboxed do
|
|
124
|
+
group = RSpec.steps "Test Steps" do
|
|
125
|
+
it { fail "All others fail" }
|
|
126
|
+
it { 1.should == 1 }
|
|
127
|
+
end
|
|
128
|
+
group.run
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
group.examples[1].metadata[:pending].should == true
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it "should allow nested steps", :pending => "Not really" do
|
|
135
|
+
group = nil
|
|
136
|
+
sandboxed do
|
|
137
|
+
group = RSpec.steps "Test Steps" do
|
|
138
|
+
steps "Nested" do
|
|
139
|
+
it { @a = 1 }
|
|
140
|
+
it { @a.should == 1}
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
group.run
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
group.children[0].examples.each do |example|
|
|
147
|
+
example.metadata[:execution_result][:status].should == 'passed'
|
|
148
|
+
end
|
|
149
|
+
group.children[0].should have(2).examples
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
it "should not allow nested normal contexts" do
|
|
153
|
+
pending "A correct approach - in the meantime, this behavior is undefined"
|
|
154
|
+
expect {
|
|
155
|
+
sandboxed do
|
|
156
|
+
RSpec.steps "Basic" do
|
|
157
|
+
describe "Not allowed" do
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
}.to raise_error
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
require 'ftools'
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
|
|
4
|
+
module FileSandbox
|
|
5
|
+
def self.included(spec)
|
|
6
|
+
return unless spec.respond_to? :before
|
|
7
|
+
|
|
8
|
+
spec.before do
|
|
9
|
+
setup_sandbox
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
spec.after do
|
|
13
|
+
teardown_sandbox
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class HaveContents
|
|
18
|
+
def initialize(contents)
|
|
19
|
+
@contents = contents
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def matches?(target)
|
|
23
|
+
case @contents
|
|
24
|
+
when Regexp
|
|
25
|
+
@contents =~ target.contents
|
|
26
|
+
when String
|
|
27
|
+
@contents == target.contents
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def have_contents(expected)
|
|
33
|
+
HaveContents.new(expected)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
attr_reader :sandbox
|
|
37
|
+
|
|
38
|
+
def in_sandbox(&block)
|
|
39
|
+
raise "I expected to create a sandbox as you passed in a block to me" if !block_given?
|
|
40
|
+
|
|
41
|
+
setup_sandbox
|
|
42
|
+
original_error = nil
|
|
43
|
+
|
|
44
|
+
begin
|
|
45
|
+
yield @sandbox
|
|
46
|
+
rescue => e
|
|
47
|
+
original_error = e
|
|
48
|
+
raise
|
|
49
|
+
ensure
|
|
50
|
+
begin
|
|
51
|
+
teardown_sandbox
|
|
52
|
+
rescue
|
|
53
|
+
if original_error
|
|
54
|
+
STDERR.puts "ALERT: a test raised an error and failed to release some lock(s) in the sandbox directory"
|
|
55
|
+
raise(original_error)
|
|
56
|
+
else
|
|
57
|
+
raise
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def setup_sandbox(path = '__sandbox')
|
|
64
|
+
unless @sandbox
|
|
65
|
+
@sandbox = Sandbox.new(path)
|
|
66
|
+
@__old_path_for_sandbox = Dir.pwd
|
|
67
|
+
Dir.chdir(@sandbox.root)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def teardown_sandbox
|
|
72
|
+
if @sandbox
|
|
73
|
+
Dir.chdir(@__old_path_for_sandbox)
|
|
74
|
+
@sandbox.clean_up
|
|
75
|
+
@sandbox = nil
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
class Sandbox
|
|
80
|
+
attr_reader :root
|
|
81
|
+
|
|
82
|
+
def initialize(path = '__sandbox')
|
|
83
|
+
@root = File.expand_path(path)
|
|
84
|
+
clean_up
|
|
85
|
+
FileUtils.mkdir_p @root
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def [](name)
|
|
89
|
+
SandboxFile.new(File.join(@root, name), name)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# usage new :file=>'my file.rb', :with_contents=>'some stuff'
|
|
93
|
+
def new(options)
|
|
94
|
+
if options.has_key? :directory
|
|
95
|
+
dir = self[options.delete(:directory)]
|
|
96
|
+
FileUtils.mkdir_p dir.path
|
|
97
|
+
else
|
|
98
|
+
file = self[options.delete(:file)]
|
|
99
|
+
if (binary_content = options.delete(:with_binary_content) || options.delete(:with_binary_contents))
|
|
100
|
+
file.binary_content = binary_content
|
|
101
|
+
else
|
|
102
|
+
file.content = (options.delete(:with_content) || options.delete(:with_contents) || '')
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
raise "unexpected keys '#{options.keys.join(', ')}'" unless options.empty?
|
|
107
|
+
|
|
108
|
+
dir || file
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def remove(options)
|
|
112
|
+
name = File.join(@root, options[:file])
|
|
113
|
+
FileUtils.remove_file name
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def clean_up
|
|
117
|
+
FileUtils.rm_rf @root
|
|
118
|
+
if File.exists? @root
|
|
119
|
+
raise "Could not remove directory #{@root.inspect}, something is probably still holding a lock on it"
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class SandboxFile
|
|
126
|
+
attr_reader :path
|
|
127
|
+
|
|
128
|
+
def initialize(path, sandbox_path)
|
|
129
|
+
@path = path
|
|
130
|
+
@sandbox_path = sandbox_path
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def inspect
|
|
134
|
+
"SandboxFile: #@sandbox_path"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def exist?
|
|
138
|
+
File.exist? path
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def content
|
|
142
|
+
File.read path
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def content=(content)
|
|
146
|
+
FileUtils.mkdir_p File.dirname(@path)
|
|
147
|
+
File.open(@path, "w") {|f| f << content}
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def binary_content=(content)
|
|
151
|
+
FileUtils.mkdir_p File.dirname(@path)
|
|
152
|
+
File.open(@path, "wb") {|f| f << content}
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def create
|
|
156
|
+
self.content = ''
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
alias exists? exist?
|
|
160
|
+
alias contents content
|
|
161
|
+
alias contents= content=
|
|
162
|
+
alias binary_contents= binary_content=
|
|
163
|
+
end
|
|
164
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
puts Dir::pwd
|
|
2
|
+
require 'test/unit'
|
|
3
|
+
begin
|
|
4
|
+
require 'spec'
|
|
5
|
+
rescue LoadError
|
|
6
|
+
false
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class RSpecTest < Test::Unit::TestCase
|
|
10
|
+
def test_that_rspec_is_available
|
|
11
|
+
assert_nothing_raised("\n\n * RSpec isn't available - please run: gem install rspec *\n\n"){ ::Spec }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def test_that_specs_pass
|
|
15
|
+
assert(system(*%w{spec -f e -p **/*.rb spec}),"\n\n * Specs failed *\n\n")
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require 'rspec/core'
|
|
2
|
+
|
|
3
|
+
class NullObject
|
|
4
|
+
private
|
|
5
|
+
def method_missing(method, *args, &block)
|
|
6
|
+
# ignore
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def sandboxed(&block)
|
|
11
|
+
@orig_config = RSpec.configuration
|
|
12
|
+
@orig_world = RSpec.world
|
|
13
|
+
new_config = RSpec::Core::Configuration.new
|
|
14
|
+
new_world = RSpec::Core::World.new(new_config)
|
|
15
|
+
RSpec.instance_variable_set(:@configuration, new_config)
|
|
16
|
+
RSpec.instance_variable_set(:@world, new_world)
|
|
17
|
+
|
|
18
|
+
load 'two-step/duckpunch/example-group.rb'
|
|
19
|
+
|
|
20
|
+
object = Object.new
|
|
21
|
+
object.extend(RSpec::Core::SharedExampleGroup)
|
|
22
|
+
object.extend(TwoStep::DSL)
|
|
23
|
+
object.extend(RSpec::Core::DSL)
|
|
24
|
+
|
|
25
|
+
(class << RSpec::Core::ExampleGroup; self; end).class_eval do
|
|
26
|
+
alias_method :orig_run, :run
|
|
27
|
+
def run(reporter=nil)
|
|
28
|
+
@orig_mock_space = RSpec::Mocks::space
|
|
29
|
+
RSpec::Mocks::space = RSpec::Mocks::Space.new
|
|
30
|
+
orig_run(reporter || NullObject.new)
|
|
31
|
+
ensure
|
|
32
|
+
RSpec::Mocks::space = @orig_mock_space
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
object.instance_eval(&block)
|
|
37
|
+
ensure
|
|
38
|
+
(class << RSpec::Core::ExampleGroup; self; end).class_eval do
|
|
39
|
+
remove_method :run
|
|
40
|
+
alias_method :run, :orig_run
|
|
41
|
+
remove_method :orig_run
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
RSpec.instance_variable_set(:@configuration, @orig_config)
|
|
45
|
+
RSpec.instance_variable_set(:@world, @orig_world)
|
|
46
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
class Ungemmer
|
|
2
|
+
def self.ungem(*names)
|
|
3
|
+
deps = names.map do |name|
|
|
4
|
+
Gem::Dependency.new(name, nil)
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
deps.each do |dep|
|
|
8
|
+
Gem.source_index.search(dep).each do |gemspec|
|
|
9
|
+
puts " ** Ungemming #{gemspec.full_name} **"
|
|
10
|
+
Gem.source_index.remove_spec(gemspec.full_name)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
Gem.instance_eval do
|
|
15
|
+
if defined? Gem::MUTEX
|
|
16
|
+
Gem::MUTEX.synchronize do
|
|
17
|
+
@searcher = nil
|
|
18
|
+
end
|
|
19
|
+
else
|
|
20
|
+
@searcher = nil
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.ungem_gemspec
|
|
26
|
+
Dir[File::expand_path(__FILE__ + "../../../*.gemspec")].each do |gemspec_path|
|
|
27
|
+
puts "Ungemming based on #{gemspec_path}"
|
|
28
|
+
begin
|
|
29
|
+
spec = Gem::Specification::load(gemspec_path)
|
|
30
|
+
Ungemmer::ungem(spec)
|
|
31
|
+
rescue LoadError
|
|
32
|
+
puts "Couldn't load #{gemspec_path}"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: two-step
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: '1.0'
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Judson Lester
|
|
8
|
+
- Evan Dorn
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2014-11-08 00:00:00.000000000 Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: corundum
|
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
|
17
|
+
requirements:
|
|
18
|
+
- - '>='
|
|
19
|
+
- !ruby/object:Gem::Version
|
|
20
|
+
version: 0.4.0
|
|
21
|
+
type: :development
|
|
22
|
+
prerelease: false
|
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
24
|
+
requirements:
|
|
25
|
+
- - '>='
|
|
26
|
+
- !ruby/object:Gem::Version
|
|
27
|
+
version: 0.4.0
|
|
28
|
+
- !ruby/object:Gem::Dependency
|
|
29
|
+
name: metric_fu
|
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
|
31
|
+
requirements:
|
|
32
|
+
- - ~>
|
|
33
|
+
- !ruby/object:Gem::Version
|
|
34
|
+
version: 4.11.1
|
|
35
|
+
type: :development
|
|
36
|
+
prerelease: false
|
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
38
|
+
requirements:
|
|
39
|
+
- - ~>
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: 4.11.1
|
|
42
|
+
- !ruby/object:Gem::Dependency
|
|
43
|
+
name: rspec
|
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - '>='
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '2.6'
|
|
49
|
+
- - <
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '3'
|
|
52
|
+
type: :runtime
|
|
53
|
+
prerelease: false
|
|
54
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
55
|
+
requirements:
|
|
56
|
+
- - '>='
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: '2.6'
|
|
59
|
+
- - <
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '3'
|
|
62
|
+
description: |2
|
|
63
|
+
I don't like Cucumber. I don't need plain text stories. My clients either
|
|
64
|
+
read code or don't read any test documents, so Cucumber is mostly useless to me.
|
|
65
|
+
But often, especially in full integration tests, it would be nice to have
|
|
66
|
+
steps in a test.
|
|
67
|
+
email:
|
|
68
|
+
- judson@lrdesign.com
|
|
69
|
+
- evan@lrdesign.com
|
|
70
|
+
executables: []
|
|
71
|
+
extensions: []
|
|
72
|
+
extra_rdoc_files:
|
|
73
|
+
- doc/README
|
|
74
|
+
- doc/Specifications
|
|
75
|
+
files:
|
|
76
|
+
- lib/two-step.rb
|
|
77
|
+
- lib/two-step/stepwise.rb
|
|
78
|
+
- lib/two-step/duckpunch/example-group.rb
|
|
79
|
+
- lib/two-step/duckpunch/example.rb
|
|
80
|
+
- lib/two-step/duckpunch/object-extensions.rb
|
|
81
|
+
- doc/README
|
|
82
|
+
- doc/Specifications
|
|
83
|
+
- spec/example_group_spec.rb
|
|
84
|
+
- spec_help/spec_helper.rb
|
|
85
|
+
- spec_help/gem_test_suite.rb
|
|
86
|
+
- spec_help/rspec-sandbox.rb
|
|
87
|
+
- spec_help/ungemmer.rb
|
|
88
|
+
- spec_help/file-sandbox.rb
|
|
89
|
+
homepage: https://github.com/LRDesign/two-step
|
|
90
|
+
licenses:
|
|
91
|
+
- MIT
|
|
92
|
+
metadata: {}
|
|
93
|
+
post_install_message:
|
|
94
|
+
rdoc_options:
|
|
95
|
+
- --inline-source
|
|
96
|
+
- --main
|
|
97
|
+
- doc/README
|
|
98
|
+
- --title
|
|
99
|
+
- two-step-1.0 RDoc
|
|
100
|
+
require_paths:
|
|
101
|
+
- lib/
|
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
103
|
+
requirements:
|
|
104
|
+
- - '>='
|
|
105
|
+
- !ruby/object:Gem::Version
|
|
106
|
+
version: '0'
|
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
108
|
+
requirements:
|
|
109
|
+
- - '>='
|
|
110
|
+
- !ruby/object:Gem::Version
|
|
111
|
+
version: '0'
|
|
112
|
+
requirements: []
|
|
113
|
+
rubyforge_project: two-step
|
|
114
|
+
rubygems_version: 2.0.14
|
|
115
|
+
signing_key:
|
|
116
|
+
specification_version: 3
|
|
117
|
+
summary: I want steps in RSpec 2.x
|
|
118
|
+
test_files:
|
|
119
|
+
- spec_help/gem_test_suite.rb
|