cukedep 0.0.1
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 +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
|