cukedep 0.0.8 → 0.1.03
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/CHANGELOG.md +26 -0
- data/bin/cukedep +1 -1
- data/lib/cukedep/application.rb +7 -7
- data/lib/cukedep/config.rb +64 -7
- data/lib/cukedep/constants.rb +5 -1
- data/lib/cukedep/cuke-runner.rb +202 -0
- data/lib/cukedep/customization.rb +35 -0
- data/lib/cukedep/feature-rep.rb +2 -2
- data/lib/cukedep/file-action.rb +211 -0
- data/lib/cukedep/hook-dsl.rb +80 -0
- data/lib/cukedep/sandbox.rb +21 -0
- data/sample/cucumber.yml +4 -0
- data/sample/model/catalogue.yml +13 -0
- data/sample/model/members.yml +13 -0
- data/sample/model/model.rb +2 -2
- data/sample/model/rentals.yml +4 -0
- data/sample/model/users.yml +7 -0
- data/sample/result.html +472 -0
- data/spec/cukedep/application_spec.rb +8 -8
- data/spec/cukedep/cuke-runner_spec.rb +80 -0
- data/spec/cukedep/customization_spec.rb +36 -0
- data/spec/cukedep/file-action_spec.rb +374 -0
- data/spec/cukedep/gherkin-facade_spec.rb +54 -0
- data/spec/cukedep/hook-dsl_spec.rb +185 -0
- data/spec/cukedep/sample_features/cukedep.rake +204 -0
- data/spec/cukedep/sample_features/cukedep_hooks.rb +30 -0
- data/spec/cukedep/sample_features/dependencies.dot +38 -0
- data/spec/cukedep/sample_features/feature2id.csv +7 -0
- data/spec/cukedep/sample_features/files_to_copy/README.md +12 -0
- data/spec/cukedep/sample_features/files_to_copy/file1.txt +5 -0
- data/spec/cukedep/sample_features/files_to_copy/file2.txt +5 -0
- data/spec/cukedep/sample_features/files_to_copy/file3.txt +9 -0
- data/spec/spec_helper.rb +1 -0
- data/templates/rake.erb +184 -160
- metadata +26 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NTMzODc5ODIyYzYyNzk4YmIxMzA0ODU3MzNkZWUxYTRkYTcwNDgxZA==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
YTE0OTg5NTlhNTI1NGFiODJlYTI0NzgyN2U1N2IyZGUzZjJmZDRhYQ==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
YWIwY2NlODYwMDE3NzRiNWRmZTE4NjUyZTc0NzkwYmY2MjdhMTU5ZWFiMmQx
|
10
|
+
ZDY5ZjkwMGRiNGFlMTk0MjNmY2IyN2YyMzBiY2UwZTFkNmQ2NmRjYTU0N2Ni
|
11
|
+
NGY1MjZmZjZiYjQzYzBjMWY3YjViMTZkYmViNTBhZWFlOGEwZTc=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
YmU1Zjg1MDA2OTdmNzg3ZDZiN2RmMWRiM2YwZTgxNTNmZGViNDJlMWY2MDZh
|
14
|
+
NjE1ZTNjZmJlNjBhOGJhYmMyNGEyODNiMzdmYTY2MTlmODhiZmU2ZGM1ZTlm
|
15
|
+
NWQwMmI4YzJiMjAwMjc1YzFiMjljMDRhZDA0YmM4MzIzNWQ4NWE=
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,29 @@
|
|
1
|
+
### 0.1.03 / 2013-11-28
|
2
|
+
* [FIX] Failing execution of generated Rake file in Travis CI. Cause: Rake template always referenced class in gem while it should reference development code while self-testing.
|
3
|
+
* [FIX] Added three missing test data files in the committed version.
|
4
|
+
|
5
|
+
### 0.1.02 / 2013-11-27
|
6
|
+
* [FIX] Failing test again in `file-action_spec.rb` file. Cause: Commit to GitHub doesn't copy empty dir!. Added directory creation code.
|
7
|
+
|
8
|
+
### 0.1.01 / 2013-11-27
|
9
|
+
* [FIX] Failing test in `file-action_spec.rb` file. Cause: Commit to GitHub doesn't copy empty dir!. Added directory creation code.
|
10
|
+
|
11
|
+
### 0.1.00 / 2013-11-27
|
12
|
+
* [FEATURE] Customized file actions (save, delete, copy) associated with invocation event
|
13
|
+
* [FEATURE] Hooks for further invocation event customization.
|
14
|
+
* [NEW] `Cukedep::CukeRunner` class. Responsibilities: -Invokes Cucumber, handles the invocation events.
|
15
|
+
* [NEW] `Cukedep::Customization` class. Responsibilities: -Loads custom hook code blocks (in `cukedep_hooks.rb`)
|
16
|
+
* [NEW] `Cukedep::FileAction` class hierarchy. Responsibilities: specify the actions to perform before/after Cucumber invocation(s).
|
17
|
+
* [NEW] `Cukedep::HookDSL` module. Used to define a DSL (Domain Specific Language)
|
18
|
+
* [NEW] `Cukedep::Sandbox`. Responsibilities: Gives the context in which hook code block are executed.
|
19
|
+
* [NEW] `Cukedep::Config` class: new methods: `load_cfg`, `write`, `file_action_attrs`
|
20
|
+
* [CHANGE] Method `Application#run!`: Using new interface of `Cukedep::Config` class.
|
21
|
+
|
22
|
+
|
23
|
+
### 0.0.9 / 2013-11-04 [unreleased]
|
24
|
+
* [CHANGE] For uniformity reasons, method `Application#start!` renamed to `Application#run!`
|
25
|
+
* [FIX] Method `Application#run!`: incorrect string interpolation in error message.
|
26
|
+
|
1
27
|
### 0.0.8 / 2013-10-31
|
2
28
|
* [Fix] After adding a non-ASCII in a sample feature, Gherkin raised an encoding incompatibility error. Fixed
|
3
29
|
* [CHANGE] Added a new field 'feature_encoding' in Config object to store the encoding of feature files (default = 'UTF-8')
|
data/bin/cukedep
CHANGED
data/lib/cukedep/application.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# File: application.rb
|
2
2
|
|
3
|
-
require 'yaml'
|
4
3
|
|
5
4
|
require_relative 'cli/cmd-line'
|
6
5
|
require_relative 'config'
|
@@ -16,17 +15,17 @@ class Application
|
|
16
15
|
public
|
17
16
|
|
18
17
|
# Entry point for the application object.
|
19
|
-
def
|
18
|
+
def run!(theCmdLineArgs)
|
20
19
|
options = options_from(theCmdLineArgs)
|
21
20
|
create_default_cfg if options[:setup]
|
22
|
-
config = load_cfg
|
21
|
+
config = Config.load_cfg(Cukedep::YMLFilename)
|
23
22
|
|
24
23
|
# Complain if no project dir is specified
|
25
24
|
if config.proj_dir.nil? || config.proj_dir.empty?
|
26
25
|
if options[:project]
|
27
26
|
@proj_dir = options[:project]
|
28
27
|
else
|
29
|
-
msg_p1 = "No project dir specified
|
28
|
+
msg_p1 = "No project dir specified in '#{Cukedep::YMLFilename}'"
|
30
29
|
msg_p2 = ' nor via --project option.'
|
31
30
|
fail(StandardError, msg_p1 + msg_p2)
|
32
31
|
end
|
@@ -62,10 +61,11 @@ protected
|
|
62
61
|
answer = $stdin.gets
|
63
62
|
exit if answer =~ /^\s*[Nn]\s*$/
|
64
63
|
end
|
65
|
-
|
64
|
+
Config.default.write(Cukedep::YMLFilename)
|
65
|
+
|
66
66
|
exit
|
67
67
|
end
|
68
|
-
|
68
|
+
=begin
|
69
69
|
# Read the .cukedep.yml file in the current working directory
|
70
70
|
def load_cfg()
|
71
71
|
if File.exist?(Cukedep::YMLFilename)
|
@@ -74,7 +74,7 @@ protected
|
|
74
74
|
Config.default
|
75
75
|
end
|
76
76
|
end
|
77
|
-
|
77
|
+
=end
|
78
78
|
# Parse the feature files (with the specified external encoding)
|
79
79
|
def parse_features(external_encoding)
|
80
80
|
# Create a Gherkin listener
|
data/lib/cukedep/config.rb
CHANGED
@@ -1,34 +1,91 @@
|
|
1
1
|
# File: config.rb
|
2
2
|
|
3
|
+
require 'yaml'
|
4
|
+
require_relative 'file-action'
|
5
|
+
|
3
6
|
module Cukedep # Module used as a namespace
|
4
7
|
|
5
8
|
FileMetaData = Struct.new(:name)
|
6
9
|
|
7
10
|
Config = Struct.new(
|
8
|
-
:feature_encoding, # The encoding of feature files
|
11
|
+
:feature_encoding, # The character encoding of feature files
|
9
12
|
:proj_dir, # The directory of the cucumber project
|
10
13
|
:feature2id, # Meta-data about the feature => feature id report
|
11
14
|
:id2feature, # Meta-data about the feature id => feature report
|
12
15
|
:graph_file, # Meta-data about the dependency graph file
|
13
16
|
:rake_file, # Name of the output rake file
|
14
|
-
:cucumber_args # Command-line syntax to use for the cucumber application
|
17
|
+
:cucumber_args, # Command-line syntax to use for the cucumber application
|
18
|
+
# File actions triggered at Cucumber invocation events
|
19
|
+
:before_all_f_actions,
|
20
|
+
:before_each_f_actions,
|
21
|
+
:after_each_f_actions,
|
22
|
+
:after_all_f_actions
|
15
23
|
)
|
16
24
|
|
17
25
|
# Re-open the class for further customisation
|
18
|
-
|
26
|
+
|
19
27
|
# Configuration object for the Cukedep application.
|
20
28
|
class Config
|
21
29
|
# Factory method. Build a config object with default settings.
|
22
30
|
def self.default()
|
23
|
-
Config.new(
|
31
|
+
instance = Config.new(
|
24
32
|
'UTF-8',
|
25
|
-
nil,
|
26
|
-
FileMetaData.new('feature2id.csv'),
|
27
|
-
FileMetaData.new('feature2id.csv'),
|
33
|
+
nil,
|
34
|
+
FileMetaData.new('feature2id.csv'),
|
35
|
+
FileMetaData.new('feature2id.csv'),
|
28
36
|
FileMetaData.new('dependencies.dot'),
|
29
37
|
'cukedep.rake',
|
30
38
|
[]
|
31
39
|
)
|
40
|
+
|
41
|
+
file_action_attrs.each do |attr|
|
42
|
+
instance[attr] = empty_action_triplet
|
43
|
+
end
|
44
|
+
|
45
|
+
return instance
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
# Read the YAML file with specified name from the current working directory.
|
50
|
+
# If that file does not exist, then return an instance with default values.
|
51
|
+
def self.load_cfg(filename)
|
52
|
+
# TODO: validation
|
53
|
+
instance = File.exist?(filename) ? YAML.load_file(filename) : self.default
|
54
|
+
|
55
|
+
return instance
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
# Save the Config object to a YAML file.
|
60
|
+
def write(filename)
|
61
|
+
File.open(filename, 'w') { |f| YAML.dump(self, f) }
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# Purpose: get the list of attributes referencing
|
68
|
+
# a file action triplet.
|
69
|
+
def self.file_action_attrs()
|
70
|
+
return [
|
71
|
+
:before_all_f_actions,
|
72
|
+
:before_each_f_actions,
|
73
|
+
:after_each_f_actions,
|
74
|
+
:after_all_f_actions
|
75
|
+
]
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
# Return Hash config for a no-op action triplet.
|
80
|
+
def self.empty_action_triplet()
|
81
|
+
{
|
82
|
+
save_patterns: [],
|
83
|
+
save_subdir: '',
|
84
|
+
delete_patterns: [],
|
85
|
+
delete_subdir: '',
|
86
|
+
copy_patterns: [],
|
87
|
+
copy_subdir: ''
|
88
|
+
}
|
32
89
|
end
|
33
90
|
|
34
91
|
end # class
|
data/lib/cukedep/constants.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
|
4
4
|
module Cukedep # Module used as a namespace
|
5
5
|
# The version number of the gem.
|
6
|
-
Version = '0.
|
6
|
+
Version = '0.1.03'
|
7
7
|
|
8
8
|
# Brief description of the gem.
|
9
9
|
Description = 'Manage dependencies between Cucumber feature files'
|
@@ -23,6 +23,10 @@ module Cukedep # Module used as a namespace
|
|
23
23
|
|
24
24
|
# The file name for the user's settings
|
25
25
|
YMLFilename = '.cukedep.yml'
|
26
|
+
|
27
|
+
# The file name for the custom block codes associated
|
28
|
+
# with before/after events.
|
29
|
+
HookFilename = 'cukedep_hooks.rb'
|
26
30
|
end
|
27
31
|
end # module
|
28
32
|
|
@@ -0,0 +1,202 @@
|
|
1
|
+
# File: cuke-runner.rb
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
require 'rake'
|
5
|
+
|
6
|
+
# Run Cucumber via specialized Rake task
|
7
|
+
require 'cucumber/rake/task'
|
8
|
+
|
9
|
+
require_relative 'file-action'
|
10
|
+
require_relative 'customization'
|
11
|
+
|
12
|
+
# UGLY workaround for bug in Cucumber's rake task
|
13
|
+
if Gem::VERSION[0].to_i >= 2 && Cucumber::VERSION <= '1.3.2'
|
14
|
+
# Monkey-patch a buggy method
|
15
|
+
class Cucumber::Rake::Task::ForkedCucumberRunner
|
16
|
+
def gem_available?(gemname)
|
17
|
+
if Gem::VERSION[0].to_i >= 2
|
18
|
+
gem_available_new_rubygems?(gemname)
|
19
|
+
else
|
20
|
+
gem_available_old_rubygems?(gemname)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end # class
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
module Cukedep # This module is used as a namespace
|
29
|
+
|
30
|
+
# Purpose: to launch Cucumber in the appropriate directory
|
31
|
+
# and pass it command-line arguments.
|
32
|
+
# Responsibilities:
|
33
|
+
# Know how to invoke Cucumber
|
34
|
+
# Know the base directory
|
35
|
+
# Know the project's root dir
|
36
|
+
class CukeRunner
|
37
|
+
# The current state of the runner.
|
38
|
+
attr_reader(:state)
|
39
|
+
|
40
|
+
# The absolute path of the root's project directory
|
41
|
+
attr_reader(:proj_dir)
|
42
|
+
attr_reader(:base_dir)
|
43
|
+
attr_reader(:config)
|
44
|
+
attr_reader(:handlers)
|
45
|
+
|
46
|
+
attr(:cucumber_opts, true)
|
47
|
+
|
48
|
+
# Constructor
|
49
|
+
def initialize(baseDir, projectDir, aConfig)
|
50
|
+
@base_dir = baseDir
|
51
|
+
@proj_dir = validated_proj_dir(projectDir)
|
52
|
+
@config = aConfig
|
53
|
+
@handlers = Customization.new.build_handlers(baseDir)
|
54
|
+
|
55
|
+
@state = :Initialized
|
56
|
+
end
|
57
|
+
|
58
|
+
# Launch Cucumber in the project directory.
|
59
|
+
def invoke()
|
60
|
+
options = [] # TODO: retrieve Cucumber options
|
61
|
+
orig_dir = Dir.getwd()
|
62
|
+
Dir.chdir(proj_dir)
|
63
|
+
|
64
|
+
begin
|
65
|
+
cuke_task = Cucumber::Rake::Task.new do |t|
|
66
|
+
t.cucumber_opts = options
|
67
|
+
end
|
68
|
+
|
69
|
+
cuke_task.runner.run()
|
70
|
+
rescue SystemExit => exc # Cucumber reports a failure.
|
71
|
+
raise StandardError, "Cucumber exited with status #{exc.status}"
|
72
|
+
ensure
|
73
|
+
Dir.chdir(orig_dir)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Event handler that is triggered
|
78
|
+
# before any other event.
|
79
|
+
# It executes the before all hook first.
|
80
|
+
# Then it executes in the following order:
|
81
|
+
# Built-in save action, Custom save action
|
82
|
+
# Built-in delete action, Custom delete action
|
83
|
+
# Built-in copy action, Custom copy action
|
84
|
+
def before_all()
|
85
|
+
expected_state(:Initialized)
|
86
|
+
|
87
|
+
# Execute before all hook code
|
88
|
+
run_code_block
|
89
|
+
|
90
|
+
# Execute file actions
|
91
|
+
builtin_actions = ActionTriplet.builtin(:before_all)
|
92
|
+
custom_actions = ActionTriplet.new(config.before_all_f_actions)
|
93
|
+
run_triplets([builtin_actions, custom_actions])
|
94
|
+
@state = :ReadyToRun
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
# Event handler that is triggered
|
99
|
+
# after any other event.
|
100
|
+
# It executes first actions in the following order:
|
101
|
+
# Built-in save action, Custom save action
|
102
|
+
# Built-in delete action, Custom delete action
|
103
|
+
# Built-in copy action, Custom copy action
|
104
|
+
# Then it executes the after all hook last.
|
105
|
+
def after_all()
|
106
|
+
expected_state(:ReadyToRun)
|
107
|
+
|
108
|
+
builtin_actions = ActionTriplet.builtin(:after_all)
|
109
|
+
custom_actions = ActionTriplet.new(config.after_all_f_actions)
|
110
|
+
run_triplets([builtin_actions, custom_actions])
|
111
|
+
|
112
|
+
# Execute before all hook code
|
113
|
+
run_code_block
|
114
|
+
@state = :Complete
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
def run!(fileNames)
|
119
|
+
expected_state(:ReadyToRun)
|
120
|
+
before_each(fileNames)
|
121
|
+
invoke
|
122
|
+
after_each
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
private
|
127
|
+
def validated_proj_dir(projectDir)
|
128
|
+
path = Pathname.new(projectDir)
|
129
|
+
path = path.expand_path if path.relative?
|
130
|
+
unless path.exist?
|
131
|
+
raise StandardError, "No such project path: '#{path}'"
|
132
|
+
end
|
133
|
+
|
134
|
+
return path.to_s
|
135
|
+
end
|
136
|
+
|
137
|
+
def expected_state(aState)
|
138
|
+
unless state == aState
|
139
|
+
msg = "expected state was '#{aState}' instead of '#{state}'."
|
140
|
+
raise StandardError, msg
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
def before_each(fileNames)
|
146
|
+
# Execute before each hook code
|
147
|
+
run_code_block(fileNames)
|
148
|
+
|
149
|
+
builtin_actions = ActionTriplet.builtin(:before_each).dup
|
150
|
+
unless builtin_actions.nil?
|
151
|
+
builtin_actions.copy_action.patterns = fileNames
|
152
|
+
end
|
153
|
+
|
154
|
+
custom_actions = ActionTriplet.new(config.before_each_f_actions)
|
155
|
+
run_triplets([builtin_actions, custom_actions])
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
def after_each()
|
160
|
+
builtin_actions = ActionTriplet.builtin(:after_each)
|
161
|
+
|
162
|
+
custom_actions = ActionTriplet.new(config.after_each_f_actions)
|
163
|
+
run_triplets([builtin_actions, custom_actions])
|
164
|
+
|
165
|
+
# Execute after each hook code
|
166
|
+
run_code_block
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
def run_triplets(theTriplets)
|
171
|
+
all_triplets = theTriplets.compact # Remove nil elements
|
172
|
+
|
173
|
+
# Do all save actions...
|
174
|
+
all_triplets.each { |t| t.save_action.run!(proj_dir, base_dir) }
|
175
|
+
|
176
|
+
# Do all delete actions...
|
177
|
+
all_triplets.each { |t| t.delete_action.run!(proj_dir) }
|
178
|
+
|
179
|
+
# Do all copy actions...
|
180
|
+
all_triplets.each { |t| t.copy_action.run!(base_dir, proj_dir) }
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
def run_code_block(*args)
|
185
|
+
# Retrieve the name of the parent method.
|
186
|
+
parent_mth = (caller[0].sub(/^(.+):in (.+)$/, "\\2"))[1..-2]
|
187
|
+
kind, scope = parent_mth.split('_')
|
188
|
+
hook_kind = (kind + '_hooks')
|
189
|
+
|
190
|
+
kode = handlers[hook_kind.to_sym][scope.to_sym]
|
191
|
+
unless kode.nil?
|
192
|
+
safe_args = args.map { |one_arg| one_arg.dup.freeze }
|
193
|
+
kode.call(*safe_args)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
|
198
|
+
end # class
|
199
|
+
|
200
|
+
end # module
|
201
|
+
|
202
|
+
# End of file
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# File: customization.rb
|
2
|
+
|
3
|
+
require_relative 'constants'
|
4
|
+
require_relative 'hook-dsl'
|
5
|
+
|
6
|
+
module Cukedep # This module is used as a namespace
|
7
|
+
|
8
|
+
|
9
|
+
class Customization
|
10
|
+
|
11
|
+
# Retrieve before/after handlers from file
|
12
|
+
# Handlers are put in a Hash with keys :before_hooks, :after_hooks.
|
13
|
+
def build_handlers(directory)
|
14
|
+
handlers = nil
|
15
|
+
|
16
|
+
filepath = directory + '/' + Cukedep::HookFilename
|
17
|
+
if File.exist? filepath
|
18
|
+
obj = Object.new
|
19
|
+
obj.extend(HookDSL)
|
20
|
+
hook_source = File.read(filepath)
|
21
|
+
obj.instance_eval(hook_source)
|
22
|
+
handlers = {
|
23
|
+
before_hooks: obj.before_hooks,
|
24
|
+
after_hooks: obj.after_hooks
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
return handlers
|
29
|
+
end
|
30
|
+
|
31
|
+
end # class
|
32
|
+
|
33
|
+
end # module
|
34
|
+
|
35
|
+
# End of file
|