cukedep 0.0.8 → 0.1.03

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +8 -8
  2. data/CHANGELOG.md +26 -0
  3. data/bin/cukedep +1 -1
  4. data/lib/cukedep/application.rb +7 -7
  5. data/lib/cukedep/config.rb +64 -7
  6. data/lib/cukedep/constants.rb +5 -1
  7. data/lib/cukedep/cuke-runner.rb +202 -0
  8. data/lib/cukedep/customization.rb +35 -0
  9. data/lib/cukedep/feature-rep.rb +2 -2
  10. data/lib/cukedep/file-action.rb +211 -0
  11. data/lib/cukedep/hook-dsl.rb +80 -0
  12. data/lib/cukedep/sandbox.rb +21 -0
  13. data/sample/cucumber.yml +4 -0
  14. data/sample/model/catalogue.yml +13 -0
  15. data/sample/model/members.yml +13 -0
  16. data/sample/model/model.rb +2 -2
  17. data/sample/model/rentals.yml +4 -0
  18. data/sample/model/users.yml +7 -0
  19. data/sample/result.html +472 -0
  20. data/spec/cukedep/application_spec.rb +8 -8
  21. data/spec/cukedep/cuke-runner_spec.rb +80 -0
  22. data/spec/cukedep/customization_spec.rb +36 -0
  23. data/spec/cukedep/file-action_spec.rb +374 -0
  24. data/spec/cukedep/gherkin-facade_spec.rb +54 -0
  25. data/spec/cukedep/hook-dsl_spec.rb +185 -0
  26. data/spec/cukedep/sample_features/cukedep.rake +204 -0
  27. data/spec/cukedep/sample_features/cukedep_hooks.rb +30 -0
  28. data/spec/cukedep/sample_features/dependencies.dot +38 -0
  29. data/spec/cukedep/sample_features/feature2id.csv +7 -0
  30. data/spec/cukedep/sample_features/files_to_copy/README.md +12 -0
  31. data/spec/cukedep/sample_features/files_to_copy/file1.txt +5 -0
  32. data/spec/cukedep/sample_features/files_to_copy/file2.txt +5 -0
  33. data/spec/cukedep/sample_features/files_to_copy/file3.txt +9 -0
  34. data/spec/spec_helper.rb +1 -0
  35. data/templates/rake.erb +184 -160
  36. metadata +26 -2
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- YTdmNmY0Y2RkMTQxYjk5NmY2OGU2ZGIzMWVkNWQ0MmQ3MTFjNTk5OA==
4
+ NTMzODc5ODIyYzYyNzk4YmIxMzA0ODU3MzNkZWUxYTRkYTcwNDgxZA==
5
5
  data.tar.gz: !binary |-
6
- NjMxMWRiMmZiOWEwYjAwMmFiZWRlYTVlZDczMTU4OThkMjM1MTE2ZA==
6
+ YTE0OTg5NTlhNTI1NGFiODJlYTI0NzgyN2U1N2IyZGUzZjJmZDRhYQ==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- OTExMGMwMzliMTA0NGU2MGU2NTdhMjNlYWQxYjg1YjJlN2NlMDBmYWZiOWZh
10
- Yzc2MGU2M2I0ZjkwOWJjZGFlZGJiMmRhNTgzMWU5N2RiNWQwMmUwN2EzYzUy
11
- ODUwOGFlZjlmOWVjYjQ4YzZlMTkzMGUwZTE1OWFlNzY3NDAzMzQ=
9
+ YWIwY2NlODYwMDE3NzRiNWRmZTE4NjUyZTc0NzkwYmY2MjdhMTU5ZWFiMmQx
10
+ ZDY5ZjkwMGRiNGFlMTk0MjNmY2IyN2YyMzBiY2UwZTFkNmQ2NmRjYTU0N2Ni
11
+ NGY1MjZmZjZiYjQzYzBjMWY3YjViMTZkYmViNTBhZWFlOGEwZTc=
12
12
  data.tar.gz: !binary |-
13
- YzU1MDgyZjk5ZGM3OGE4NTMxNDMwYTk5MWQyNGRmZDY1MGQwMWIyMTI0YmU0
14
- YTkxZWYzMTE0N2I0NDVkYjkzMDJjZjhiOTRlMGNmZDJkZmE2NjQxMDRmNWMw
15
- MjM4NGJlZDQ4YmYwZWJkMzhkZDkzOWE5OWY1Mjk0NjI2NDgxNmM=
13
+ YmU1Zjg1MDA2OTdmNzg3ZDZiN2RmMWRiM2YwZTgxNTNmZGViNDJlMWY2MDZh
14
+ NjE1ZTNjZmJlNjBhOGJhYmMyNGEyODNiMzdmYTY2MTlmODhiZmU2ZGM1ZTlm
15
+ NWQwMmI4YzJiMjAwMjc1YzFiMjljMDRhZDA0YmM4MzIzNWQ4NWE=
@@ -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')
@@ -10,6 +10,6 @@ require 'cukedep' # Load the Cukedep::Application class
10
10
 
11
11
  # The application's entry point
12
12
  app = Cukedep::Application.new
13
- app.start!(ARGV)
13
+ app.run!(ARGV)
14
14
 
15
15
  # End of file
@@ -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 start!(theCmdLineArgs)
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 via 'Cukedep::YMLFilename'"
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
- File.open(Cukedep::YMLFilename, 'w') { |f| YAML.dump(Config.default, f) }
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
@@ -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
@@ -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.0.8'
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