cukedep 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.rspec +1 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.simplecov +7 -0
- data/.travis.yml +15 -0
- data/.yardopts +6 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +19 -0
- data/README.md +64 -0
- data/Rakefile +32 -0
- data/bin/cukedep +14 -0
- data/cucumber.yml +10 -0
- data/lib/cukedep.rb +9 -0
- data/lib/cukedep/application.rb +112 -0
- data/lib/cukedep/cli/application.rb +0 -0
- data/lib/cukedep/cli/cmd-line.rb +115 -0
- data/lib/cukedep/config.rb +31 -0
- data/lib/cukedep/constants.rb +29 -0
- data/lib/cukedep/feature-model.rb +285 -0
- data/lib/cukedep/feature-rep.rb +49 -0
- data/lib/cukedep/gherkin-listener.rb +98 -0
- data/lib/macros4cuke.rb +8 -0
- data/sample/features/step_definitions/steps.rb +105 -0
- data/sample/features/support/env.rb +12 -0
- data/sample/model/model.rb +216 -0
- data/spec/cukedep/application_spec.rb +81 -0
- data/spec/cukedep/cli/cmd-line_spec.rb +91 -0
- data/spec/cukedep/feature-model_spec.rb +103 -0
- data/spec/cukedep/feature-rep_spec.rb +53 -0
- data/spec/cukedep/file-parsing.rb +40 -0
- data/spec/cukedep/gherkin-listener_spec.rb +59 -0
- data/spec/cukedep/sample_features/a_few_tests.feature +24 -0
- data/spec/cukedep/sample_features/more_tests.feature +24 -0
- data/spec/cukedep/sample_features/other_tests.feature +15 -0
- data/spec/cukedep/sample_features/some_tests.feature +13 -0
- data/spec/cukedep/sample_features/standalone.feature +19 -0
- data/spec/cukedep/sample_features/still_other_tests.feature +24 -0
- data/spec/cukedep/sample_features/yet_other_tests.feature +19 -0
- data/spec/spec_helper.rb +18 -0
- data/templates/rake.erb +163 -0
- metadata +165 -0
data/lib/macros4cuke.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
# File: steps.rb
|
2
|
+
# Step definitions for a sample Cucumber application
|
3
|
+
|
4
|
+
Given(/^the catalogue is empty$/) do
|
5
|
+
$store.zap_catalogue!()
|
6
|
+
end
|
7
|
+
|
8
|
+
Given(/^I add the video "(.*?)" to the catalogue$/) do |a_title|
|
9
|
+
$store.add_video(a_title)
|
10
|
+
end
|
11
|
+
|
12
|
+
Then(/^I should see the video "(.*?)" as unknown$/) do |a_title|
|
13
|
+
$store.search_video(a_title).should be_nil
|
14
|
+
end
|
15
|
+
|
16
|
+
Then(/^I should see the video "(.*?)" as (available)$/) do |a_title, a_state|
|
17
|
+
found_video = $store.search_video(a_title)
|
18
|
+
found_video.state.should == a_state.to_sym
|
19
|
+
end
|
20
|
+
|
21
|
+
When(/^I remove the video "(.*?)"$/) do |a_title|
|
22
|
+
found_video = $store.search_video(a_title)
|
23
|
+
found_video.should_not be_nil
|
24
|
+
found_video.state.should == :available
|
25
|
+
$store.remove_video(found_video)
|
26
|
+
end
|
27
|
+
|
28
|
+
Given(/^there is no member yet$/) do
|
29
|
+
$store.send(:zap_members!) # Why is this method seen as private?
|
30
|
+
end
|
31
|
+
|
32
|
+
Then(/^I should see member "(.*?)" as unknown$/) do |member_name|
|
33
|
+
$store.search_member(member_name).should be_nil
|
34
|
+
end
|
35
|
+
|
36
|
+
Then(/^I should see member "(.*?)" as registered$/) do |member_name|
|
37
|
+
$store.search_member(member_name).should_not be_nil
|
38
|
+
puts "Member #{member_name} is registered."
|
39
|
+
end
|
40
|
+
|
41
|
+
Given(/^I subscribe "(.*?)"$/) do |member_name|
|
42
|
+
$store.add_member(member_name)
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
Given(/^there is no registered user$/) do
|
47
|
+
$store.zap_users!
|
48
|
+
end
|
49
|
+
|
50
|
+
When(/^I enter the credentials "(.*?)"$/) do |credential|
|
51
|
+
@entered_credential = credential
|
52
|
+
end
|
53
|
+
|
54
|
+
Then(/^I should not be authorized$/) do
|
55
|
+
$store.search_user(@entered_credential).should be_nil
|
56
|
+
puts "Invalid user credential"
|
57
|
+
end
|
58
|
+
|
59
|
+
When(/^I register my credentials "(.*?)"$/) do |credential|
|
60
|
+
$store.add_user(credential)
|
61
|
+
end
|
62
|
+
|
63
|
+
Then(/^I should see a welcome message$/) do
|
64
|
+
$store.search_user(@entered_credential).should_not be_nil
|
65
|
+
puts "Welcome to the rental application."
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
When(/^I register the rental of "(.*?)" for "(.*?)"$/) do |a_title, member_name|
|
70
|
+
found_video = $store.search_video(a_title)
|
71
|
+
found_video.should_not be_nil
|
72
|
+
found_video.state.should == :available
|
73
|
+
|
74
|
+
member = $store.search_member(member_name)
|
75
|
+
member.should_not be_nil
|
76
|
+
$store.add_rental(found_video, member)
|
77
|
+
@confirm_rental = true
|
78
|
+
end
|
79
|
+
|
80
|
+
Then(/^I should see the rental confirmed$/) do
|
81
|
+
puts "Rental registered." if @confirm_rental
|
82
|
+
@confirm_rental = nil
|
83
|
+
end
|
84
|
+
|
85
|
+
Then(/^I should see the rental refused$/) do
|
86
|
+
puts "Rental refused." unless @confirm_rental
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
When(/^I register the return of "(.*?)" from "(.*?)"$/) do |a_title, member_name|
|
91
|
+
rental = $store.search_rental(a_title)
|
92
|
+
rental.should_not be_nil
|
93
|
+
rental.member.should == member_name
|
94
|
+
$store.close_rental(rental)
|
95
|
+
@confirm_return = true
|
96
|
+
end
|
97
|
+
|
98
|
+
Then(/^I should see the return confirmed$/) do
|
99
|
+
puts "Return registered." if @confirm_return
|
100
|
+
@confirm_return = nil
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
|
105
|
+
# End of file
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# File: env.rb
|
2
|
+
|
3
|
+
require 'rspec/expectations'
|
4
|
+
require_relative '../../model/model'
|
5
|
+
|
6
|
+
AfterConfiguration do |_|
|
7
|
+
# Quick and dirty implementation: use a global variable
|
8
|
+
# as an entry point to the domain model.
|
9
|
+
$store = Sample::RentalStore.new
|
10
|
+
end
|
11
|
+
|
12
|
+
# End of file
|
@@ -0,0 +1,216 @@
|
|
1
|
+
# File: model.rb
|
2
|
+
|
3
|
+
require 'pp'
|
4
|
+
require 'yaml' # Rely on YAML for object persistence
|
5
|
+
|
6
|
+
|
7
|
+
# Module used as a namespace
|
8
|
+
module Sample
|
9
|
+
|
10
|
+
# Assumption: the store has one examplar only of a video title
|
11
|
+
Video = Struct.new(
|
12
|
+
:title, # Title of the video (as an identifier)
|
13
|
+
:state # Current state of the video
|
14
|
+
)
|
15
|
+
|
16
|
+
# A member of the video store, i.e. a person that is allowed
|
17
|
+
# to rent a video from the store.
|
18
|
+
Member = Struct.new(
|
19
|
+
:name # As an identifier
|
20
|
+
)
|
21
|
+
|
22
|
+
# Association object between a Video and a Member.
|
23
|
+
# In our sample model, no rental history is performed
|
24
|
+
Rental = Struct.new(
|
25
|
+
:video, # Use the title of the rented video as key
|
26
|
+
:member # Use the name of the Member as key
|
27
|
+
)
|
28
|
+
|
29
|
+
# The identification of a store rental employee
|
30
|
+
# that is authorized to use the rental software.
|
31
|
+
User = Struct.new(
|
32
|
+
:credential # user credential
|
33
|
+
)
|
34
|
+
|
35
|
+
# Simplistic domain model of a video rental store.
|
36
|
+
class RentalStore
|
37
|
+
MyDir = File.dirname(__FILE__) + '/'
|
38
|
+
CatalogueFile = 'catalogue.yml'
|
39
|
+
MembersFile = 'members.yml'
|
40
|
+
RentalsFile = 'rentals.yml'
|
41
|
+
UsersFile = 'users.yml'
|
42
|
+
|
43
|
+
|
44
|
+
# The list of all videos owned by the store.
|
45
|
+
attr_reader(:catalogue)
|
46
|
+
|
47
|
+
# The list of all Members
|
48
|
+
attr_reader(:members)
|
49
|
+
|
50
|
+
# The list of all open-standing rentals
|
51
|
+
attr_reader(:rentals)
|
52
|
+
|
53
|
+
# The list of application user (credentials)
|
54
|
+
attr_reader(:users)
|
55
|
+
|
56
|
+
def initialize()
|
57
|
+
init_catalogue()
|
58
|
+
init_members()
|
59
|
+
init_rentals()
|
60
|
+
init_users()
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
# Clear the catalogue (= make it empty).
|
65
|
+
def zap_catalogue!()
|
66
|
+
catalogue.clear()
|
67
|
+
save_catalogue()
|
68
|
+
zap_rentals!()
|
69
|
+
end
|
70
|
+
|
71
|
+
def zap_members!()
|
72
|
+
members.clear()
|
73
|
+
save_members()
|
74
|
+
zap_rentals!()
|
75
|
+
end
|
76
|
+
|
77
|
+
def zap_users!()
|
78
|
+
users.clear()
|
79
|
+
save_users()
|
80
|
+
end
|
81
|
+
|
82
|
+
# Search a Video object having the given title
|
83
|
+
def search_video(aTitle)
|
84
|
+
result = catalogue.find {|video| video.title.strip == aTitle.strip}
|
85
|
+
if result.nil?
|
86
|
+
msg = "Video with title '#{aTitle}' isn't in the catalogue."
|
87
|
+
$stderr.puts msg
|
88
|
+
end
|
89
|
+
|
90
|
+
return result
|
91
|
+
end
|
92
|
+
|
93
|
+
def remove_video(aVideo)
|
94
|
+
catalogue.delete(aVideo)
|
95
|
+
save_catalogue()
|
96
|
+
end
|
97
|
+
|
98
|
+
# Add a new video to the catalogue
|
99
|
+
def add_video(aTitle)
|
100
|
+
# Simplification: no check for title collision
|
101
|
+
catalogue << Video.new(aTitle, :available)
|
102
|
+
save_catalogue()
|
103
|
+
end
|
104
|
+
|
105
|
+
def search_member(aName)
|
106
|
+
mb = members.find {|person| person.name.strip == aName.strip}
|
107
|
+
puts "No member with name #{aName}." if mb.nil?
|
108
|
+
|
109
|
+
return mb
|
110
|
+
end
|
111
|
+
|
112
|
+
def add_member(aName)
|
113
|
+
# Simplification: no check for name collision
|
114
|
+
members << Member.new(aName)
|
115
|
+
save_members()
|
116
|
+
end
|
117
|
+
|
118
|
+
def search_user(aCredential)
|
119
|
+
users.find {|user| user.credential.strip == aCredential.strip}
|
120
|
+
end
|
121
|
+
|
122
|
+
def add_user(aCredential)
|
123
|
+
# Simplification: no check for credential collision
|
124
|
+
users << User.new(aCredential)
|
125
|
+
save_users()
|
126
|
+
end
|
127
|
+
|
128
|
+
def search_rental(aTitle)
|
129
|
+
rentals.find {|r| r.video.strip == aTitle.strip}
|
130
|
+
end
|
131
|
+
|
132
|
+
def add_rental(aVideo, aMember)
|
133
|
+
rentals << Rental.new(aVideo.title, aMember.name)
|
134
|
+
aVideo.state = :rented
|
135
|
+
save_rentals
|
136
|
+
end
|
137
|
+
|
138
|
+
def close_rental(aRental)
|
139
|
+
rentals.delete(aRental)
|
140
|
+
save_rentals
|
141
|
+
returned_video = search_video(aRental.video)
|
142
|
+
returned_video.state = :available
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
def init_catalogue()
|
147
|
+
filepath = MyDir + CatalogueFile
|
148
|
+
if File.exist?(filepath)
|
149
|
+
@catalogue = YAML.load_file(filepath)
|
150
|
+
else
|
151
|
+
@catalogue = []
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def init_members()
|
156
|
+
filepath = MyDir + MembersFile
|
157
|
+
if File.exist?(filepath)
|
158
|
+
@members = YAML.load_file(filepath)
|
159
|
+
else
|
160
|
+
@members = []
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def init_users()
|
165
|
+
filepath = MyDir + UsersFile
|
166
|
+
if File.exist?(filepath)
|
167
|
+
@users = YAML.load_file(filepath)
|
168
|
+
else
|
169
|
+
@users = []
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def init_rentals()
|
174
|
+
filepath = MyDir + RentalsFile
|
175
|
+
if File.exist?(filepath)
|
176
|
+
@rentals = YAML.load_file(filepath)
|
177
|
+
else
|
178
|
+
@rentals = []
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def zap_rentals!()
|
183
|
+
rentals.clear
|
184
|
+
save_rentals()
|
185
|
+
end
|
186
|
+
|
187
|
+
def zap_members!()
|
188
|
+
members.clear
|
189
|
+
save_members()
|
190
|
+
end
|
191
|
+
|
192
|
+
def save_catalogue()
|
193
|
+
filepath = MyDir + CatalogueFile
|
194
|
+
File.open(filepath, 'w') {|f| YAML.dump(catalogue, f)}
|
195
|
+
end
|
196
|
+
|
197
|
+
def save_members()
|
198
|
+
filepath = MyDir + MembersFile
|
199
|
+
File.open(filepath, 'w') {|f| YAML.dump(members, f)}
|
200
|
+
end
|
201
|
+
|
202
|
+
def save_users()
|
203
|
+
filepath = MyDir + UsersFile
|
204
|
+
File.open(filepath, 'w') {|f| YAML.dump(users, f)}
|
205
|
+
end
|
206
|
+
|
207
|
+
def save_rentals()
|
208
|
+
filepath = MyDir + RentalsFile
|
209
|
+
File.open(filepath, 'w') {|f| YAML.dump(rentals, f)}
|
210
|
+
end
|
211
|
+
|
212
|
+
end # class
|
213
|
+
|
214
|
+
end # module
|
215
|
+
|
216
|
+
# End of file
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# File: cmd-line_spec.rb
|
2
|
+
|
3
|
+
require 'stringio'
|
4
|
+
require_relative '../spec_helper'
|
5
|
+
|
6
|
+
# Load the class under testing
|
7
|
+
require_relative '../../lib/cukedep/application'
|
8
|
+
|
9
|
+
module Cukedep # Open module to get rid of long qualified names
|
10
|
+
|
11
|
+
describe Application do
|
12
|
+
|
13
|
+
context 'Creation & initialization:' do
|
14
|
+
it 'should be created without argument' do
|
15
|
+
expect { Application.new }.not_to raise_error
|
16
|
+
end
|
17
|
+
end # context
|
18
|
+
|
19
|
+
context 'Provided services:' do
|
20
|
+
subject { Application.new }
|
21
|
+
|
22
|
+
it 'should read its command-line' do
|
23
|
+
options = subject.send(:options_from, %w[])
|
24
|
+
expect(options).to be_empty
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should generate a user settings file' do
|
28
|
+
# Start state: no config
|
29
|
+
File.delete(Cukedep::YMLFilename) if File.exist?(Cukedep::YMLFilename)
|
30
|
+
|
31
|
+
# --setup option creates the config file then stops the application
|
32
|
+
expect { subject.start!(['--setup'])}.to raise_error(SystemExit)
|
33
|
+
|
34
|
+
# Check that the config file was effectively created.
|
35
|
+
expect { File.exist?(Cukedep::YMLFilename) }.to be_true
|
36
|
+
created_config = subject.send(:load_cfg)
|
37
|
+
expect(created_config).to eq(Config.default)
|
38
|
+
|
39
|
+
# Re-run again with --setup option.
|
40
|
+
# It should ask permission for overwriting
|
41
|
+
# Capture console IO
|
42
|
+
old_stdout = $>
|
43
|
+
ostream = StringIO.new('rw')
|
44
|
+
$> = ostream
|
45
|
+
old_stdin = $stdin
|
46
|
+
$stdin = StringIO.new("n\n", 'r')
|
47
|
+
expect { subject.start!(['--setup'])}.to raise_error(SystemExit)
|
48
|
+
$> = old_stdout
|
49
|
+
$sdtin = old_stdin
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should complain in absence of project dir' do
|
53
|
+
# Start state: no config
|
54
|
+
File.delete(Cukedep::YMLFilename) if File.exist?(Cukedep::YMLFilename)
|
55
|
+
expect(File.exist?(Cukedep::YMLFilename)).to be_false
|
56
|
+
|
57
|
+
# Create default config
|
58
|
+
expect { subject.start!(['--setup'])}.to raise_error(SystemExit)
|
59
|
+
|
60
|
+
err = StandardError
|
61
|
+
err_msg = "No project dir specified via 'Cukedep::YMLFilename' nor via --project option."
|
62
|
+
expect {subject.start!([])}.to raise_error(err, err_msg)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should parse the feature files' do
|
66
|
+
curr_dir = Dir.getwd()
|
67
|
+
begin
|
68
|
+
file_dir = File.dirname(__FILE__)
|
69
|
+
Dir.chdir(file_dir + '/sample_features')
|
70
|
+
unless File.exist?(Cukedep::YMLFilename)
|
71
|
+
expect { subject.start!(['--setup'])}.to raise_error(SystemExit)
|
72
|
+
end
|
73
|
+
subject.start!(['--project', '../../../sample'])
|
74
|
+
ensure
|
75
|
+
Dir.chdir(curr_dir)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end # context
|
79
|
+
end # describe
|
80
|
+
|
81
|
+
end # module
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# File: cmd-line_spec.rb
|
2
|
+
|
3
|
+
require 'stringio'
|
4
|
+
require_relative '../../spec_helper'
|
5
|
+
|
6
|
+
# Load the class under testing
|
7
|
+
require_relative '../../../lib/cukedep/cli/cmd-line'
|
8
|
+
|
9
|
+
module Cukedep # Open module to get rid of long qualified names
|
10
|
+
|
11
|
+
describe CLI::CmdLine do
|
12
|
+
|
13
|
+
context 'Creation & initialization:' do
|
14
|
+
subject { CLI::CmdLine.new }
|
15
|
+
|
16
|
+
it 'should be created without argument' do
|
17
|
+
expect { CLI::CmdLine.new }.not_to raise_error
|
18
|
+
end
|
19
|
+
end # context
|
20
|
+
|
21
|
+
context 'Provided services:' do
|
22
|
+
def capture_output()
|
23
|
+
@output = $>
|
24
|
+
ostream = StringIO.new('rw')
|
25
|
+
$> = ostream
|
26
|
+
end
|
27
|
+
|
28
|
+
def release_output()
|
29
|
+
$> = @output
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
it 'should accept an empty command-line' do
|
34
|
+
expect { subject.parse!([]) }.not_to raise_error
|
35
|
+
expect(subject.options).to be_empty
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should accept the dry-run option' do
|
39
|
+
expect { subject.parse!(['--dry-run']) }.not_to raise_error
|
40
|
+
expect(subject.options).to eq({ :"dry-run" => true })
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should accept the setup option' do
|
44
|
+
expect { subject.parse!(['--setup']) }.not_to raise_error
|
45
|
+
expect(subject.options).to eq({ :setup => true })
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should validate the project option argument' do
|
49
|
+
# Case 1: missing project dir argument
|
50
|
+
cmd_opts = ['--project']
|
51
|
+
err_type = StandardError
|
52
|
+
err_msg = <<-MSG_END
|
53
|
+
No argument provided with command line option: --project
|
54
|
+
To see the command-line syntax, do:
|
55
|
+
cukedep --help
|
56
|
+
MSG_END
|
57
|
+
expect { subject.parse!(cmd_opts) }.to raise_error(err_type)
|
58
|
+
|
59
|
+
# Case 2: non existing project dir
|
60
|
+
cmd_opts = ['--project', 'nowhere']
|
61
|
+
err_msg = "Cannot find the directory 'nowhere'."
|
62
|
+
expect { subject.parse!(cmd_opts) }.to raise_error(err_type, err_msg)
|
63
|
+
|
64
|
+
# Case 3: project dir exists
|
65
|
+
#cmd_opts = ['--project', '../../../sample']
|
66
|
+
#expect { subject.parse!(cmd_opts) }.not_to raise_error
|
67
|
+
#expect(subject.options).to eq({ :project => '../../../sample' })
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should handle the version option' do
|
71
|
+
capture_output
|
72
|
+
cmd_opts = ['--version']
|
73
|
+
expect { subject.parse!(cmd_opts) }.to raise_error(SystemExit)
|
74
|
+
expect($>.string).to eq(Cukedep::Version + "\n")
|
75
|
+
release_output
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'should handle the help option' do
|
79
|
+
capture_output
|
80
|
+
cmd_opts = ['--help']
|
81
|
+
expect { subject.parse!(cmd_opts) }.to raise_error(SystemExit)
|
82
|
+
expect($>.string).to eq(subject.parser.to_s)
|
83
|
+
release_output
|
84
|
+
end
|
85
|
+
end # context
|
86
|
+
|
87
|
+
|
88
|
+
|
89
|
+
end # describe
|
90
|
+
|
91
|
+
end # module
|