diddy 0.7.0 → 0.8.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.
data/diddy.gemspec CHANGED
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
 
5
5
  Gem::Specification.new do |gem|
6
6
  gem.name = "diddy"
7
- gem.version = '0.7.0'
7
+ gem.version = '0.8.0'
8
8
  gem.authors = ["Diederick Lawson", "Marcel de Graaf"]
9
9
  gem.email = ["diederick@altovista.nl", "mail@marceldegraaf.net"]
10
10
  gem.description = %q{Diddy script runner}
@@ -16,5 +16,5 @@ Gem::Specification.new do |gem|
16
16
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
17
  gem.require_paths = ["lib"]
18
18
 
19
- gem.add_development_dependency "term/aniscolor"
19
+ gem.add_development_dependency "term/ansicolor"
20
20
  end
@@ -0,0 +1,9 @@
1
+ module Diddy
2
+ class Context
3
+ attr_accessor :options
4
+
5
+ def initialize(options)
6
+ self.options = options
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,119 @@
1
+ module Diddy
2
+ class RunResult
3
+ attr_accessor :scripts
4
+
5
+ #
6
+ # Starts the run of a script. Call this before running a scenario.
7
+ #
8
+ def run_script(description)
9
+ @scripts ||= []
10
+ @current_script = Script.new(description)
11
+ @scripts << @current_script
12
+ end
13
+
14
+ #
15
+ # Start the run of an entire scenario.
16
+ #
17
+ def run_scenario(description)
18
+ @current_scenario = Scenario.new(description)
19
+ @current_scenario.script = @current_script
20
+
21
+ @current_script.scenarios << @current_scenario
22
+ end
23
+
24
+ #
25
+ # Starts run of a step
26
+ #
27
+ def run_step(description)
28
+ @current_step = Step.new(description)
29
+ @current_step.scenario = @current_scenario
30
+
31
+ @current_scenario.steps << @current_step
32
+ end
33
+
34
+ #
35
+ # Starts run of a sub step
36
+ #
37
+ def run_sub_step(description)
38
+ @current_sub_step = SubStep.new(description)
39
+ @current_sub_step.step = @current_step
40
+
41
+ @current_step.sub_steps << @current_sub_step
42
+ end
43
+
44
+ #
45
+ # Sets the result of the current step
46
+ #
47
+ def set_step_result(result)
48
+ @current_step.result = result
49
+ end
50
+
51
+ #
52
+ # Sets the result to false and logs the exception of the step
53
+ #
54
+ def set_step_exception(exception)
55
+ @current_step.result = false
56
+ @current_step.exception = exception
57
+ end
58
+
59
+ #
60
+ # Sets the result of the sub step
61
+ #
62
+ def set_sub_step_result(result)
63
+ @current_sub_step.result = result
64
+ end
65
+
66
+ # helper classes
67
+ class Script
68
+ attr_accessor :scenarios, :description
69
+
70
+ def initialize(description)
71
+ self.description = description
72
+ end
73
+
74
+ def scenarios
75
+ @scenarios ||= []
76
+ end
77
+
78
+ def result
79
+ scenarios.all? { |scenario| scenario.result }
80
+ end
81
+ end
82
+
83
+ class Scenario
84
+ attr_accessor :steps, :description, :script
85
+
86
+ def initialize(description)
87
+ self.description = description
88
+ end
89
+
90
+ def steps
91
+ @steps ||= []
92
+ end
93
+
94
+ def result
95
+ steps.all? { |step| step.result }
96
+ end
97
+ end
98
+
99
+ class Step
100
+ attr_accessor :description, :sub_steps, :result, :scenario, :exception
101
+
102
+ def initialize(description)
103
+ self.description = description
104
+ end
105
+
106
+ def sub_steps
107
+ @sub_steps ||= []
108
+ end
109
+ end
110
+
111
+ class SubStep
112
+ attr_accessor :description, :result, :step
113
+
114
+ def initialize(description)
115
+ self.description = description
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,74 @@
1
+ module Diddy
2
+ class RunResultPrinter
3
+ attr_accessor :run_result
4
+
5
+ def initialize(run_result)
6
+ self.run_result = run_result
7
+ end
8
+
9
+ #
10
+ # Converts the result to HTML
11
+ #
12
+ # @TODO needs to be refactored to a templating language. Quick hack for now.
13
+ #
14
+ def to_html
15
+ html = "<html><head><style type='text/css'>.error { color: red }; pre { display: block; margin: 1em; };</style><title>Run results</title></head><body>"
16
+
17
+ # walk over all scripts
18
+ run_result.scripts.each do |script|
19
+ html << "<h2>#{script.description}</h2>"
20
+
21
+ # walk of the scenarios
22
+ script.scenarios.each do |scenario|
23
+ # result was ok? than only log the scenario itself
24
+ if scenario.result
25
+ html << "<h3>#{scenario.description}</h3>"
26
+ else
27
+ # log the error and it's steps
28
+ html << "<h3 class='error'>#{scenario.description}</h3>"
29
+ html << "<ul>"
30
+
31
+ # walk over the steps
32
+ scenario.steps.each do |step|
33
+ # output step
34
+ if step.result
35
+ html << "<li>#{step.description}"
36
+ else
37
+ html << "<li class='error'>#{step.description}"
38
+ end
39
+
40
+
41
+ # was there an exception?
42
+ if step.exception
43
+ html << "<pre>#{step.exception.message}\n\n#{step.exception.backtrace.join("\n")}</pre>"
44
+ end
45
+
46
+ # are there any sub steps?
47
+ if step.sub_steps.any?
48
+ html << "<ul>"
49
+
50
+ # walk over sub steps
51
+ step.sub_steps.each do |sub_step|
52
+ if sub_step.result
53
+ html << "<li>#{sub_step.description}</li>"
54
+ else
55
+ html << "<li class='error'>#{sub_step.description}</li>"
56
+ end
57
+ end
58
+
59
+ html << "</ul></li>"
60
+ else
61
+ html << "</li>"
62
+ end
63
+ end
64
+
65
+ html << "</ul>"
66
+ end
67
+ end
68
+ end
69
+
70
+ html << "</body>"
71
+ html
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,113 @@
1
+ module Diddy
2
+ #
3
+ # A scenario contains several steps. This is where the real work is done.
4
+ #
5
+ class Scenario
6
+ attr_accessor :script, :context, :description, :steps, :run_result
7
+
8
+ #
9
+ # Creates a scenario
10
+ #
11
+ def initialize(options = {})
12
+ options.each { |k,v| send("#{k}=", v) }
13
+ end
14
+
15
+ #
16
+ # Determines which step classes should be used
17
+ #
18
+ def uses(klass)
19
+ @steps_instances ||= []
20
+ @steps_instances << klass.new(shared_scope)
21
+ end
22
+
23
+ #
24
+ # Describes which step should be run
25
+ #
26
+ def step(description)
27
+ @steps ||= []
28
+
29
+ # find step klass
30
+ steps_instance = find_steps_instance_for(description)
31
+
32
+ # check if step exists
33
+ if steps_instance && steps_instance.class.has_step?(description)
34
+ @steps << Diddy::Step.new(
35
+ description: description,
36
+ steps_instance: steps_instance,
37
+ definition: steps_instance.class.definition(description)
38
+ )
39
+ else
40
+ raise "Step '#{description}' not defined"
41
+ end
42
+ end
43
+
44
+ #
45
+ # Runs all the steps in the script
46
+ #
47
+ def run
48
+ begin
49
+ @steps.each do |step|
50
+ run_step(step)
51
+ end
52
+
53
+ true
54
+
55
+ rescue ScenarioAborted
56
+ false
57
+
58
+ end
59
+ end
60
+
61
+ #
62
+ # Runs one step
63
+ #
64
+ def run_step(step)
65
+ # run proc on this instance as scope
66
+ begin
67
+ step.run_result = run_result
68
+ result = step.run
69
+
70
+ rescue Exception => exception
71
+ print_exception(exception)
72
+ run_result.set_step_exception(exception)
73
+
74
+ raise ScenarioAborted.new
75
+ end
76
+
77
+ unless result
78
+ raise ScenarioAborted.new
79
+ end
80
+ end
81
+
82
+ #
83
+ # Prints exception thrown by step, on screen
84
+ #
85
+ def print_exception(exception)
86
+ print("\n")
87
+
88
+ # print backtrace
89
+ puts("- #{exception.message}")
90
+ puts(" #{exception.backtrace.join("\n ")}")
91
+ puts("\n")
92
+ end
93
+
94
+ #
95
+ # Finds the instance of the steps definition by step name
96
+ #
97
+ def find_steps_instance_for(description)
98
+ @steps_instances.each do |instance|
99
+ if instance.class.steps && instance.class.steps.has_key?(description)
100
+ return instance
101
+ end
102
+ end
103
+
104
+ nil
105
+ end
106
+
107
+ def shared_scope
108
+ @shared_scope ||= SharedScope.new
109
+ end
110
+
111
+ class ScenarioAborted < Exception; end
112
+ end
113
+ end
data/lib/diddy/script.rb CHANGED
@@ -1,216 +1,119 @@
1
1
  # encoding: utf-8
2
2
  module Diddy
3
+ #
4
+ # A script contains several scenarios. A script can be a recipe, an application etc.
5
+ #
3
6
  class Script
4
- STATE_OK = 1
5
- STATE_FAILED = 2
6
- STATE_EXCEPTION = 3
7
+ attr_accessor :scenarios, :description, :context, :run_result
7
8
 
8
- attr_accessor :steps, :scenario, :log
9
-
10
- #
11
- # Creates a script
12
- #
13
- def initialize(scenario)
14
- @scenario = scenario
9
+ def initialize(description)
10
+ self.description = description
15
11
  end
16
12
 
17
13
  #
18
- # Determines which step classes should be used
14
+ # Defines a new scenario
19
15
  #
20
- def uses(klass)
21
- @steps_instances ||= []
22
- @steps_instances << klass.new(shared_scope)
23
- end
24
-
16
+ # scenario('Do something') do
17
+ # uses SomeSteps
25
18
  #
26
- # Full log
19
+ # step 'Do this'
20
+ # step 'Do that'
21
+ # end
27
22
  #
28
- def log
29
- @log ||= ""
30
- end
23
+ def scenario(description, &block)
24
+ @scenarios ||= []
31
25
 
32
- #
33
- # Describes which step should be run
34
- #
35
- def step(description)
36
- @steps ||= []
37
-
38
- # find step klass
39
- steps_instance = find_steps_instance_for(description)
40
-
41
- # check if step exists
42
- if steps_instance && steps_instance.class.has_step?(description)
43
- @steps << Diddy::Step.new(
44
- description: description,
45
- steps_instance: steps_instance,
46
- definition: steps_instance.class.definition(description)
47
- )
48
- else
49
- raise "Step '#{description}' not defined"
50
- end
26
+ # instantiate scenario
27
+ scenario = Scenario.new(
28
+ script: self,
29
+ context: context,
30
+ description: description,
31
+ run_result: run_result
32
+ )
33
+
34
+ # run the DSL
35
+ scenario.instance_eval(&block)
36
+
37
+ # add to our collection of scenarios
38
+ @scenarios << scenario
51
39
  end
52
40
 
53
41
  #
54
- # Runs all the steps in the script
42
+ # Run the script
55
43
  #
56
44
  def run
57
- begin
58
- self.class.write_log(scenario)
45
+ scenarios.each_with_index do |scenario, index|
46
+ # print on screen
47
+ puts("Scenario #{index + 1}: #{scenario.description}")
59
48
 
60
- @steps.each do |step|
61
- run_step(step)
62
- end
63
- return true
49
+ # also log
50
+ run_result.run_scenario("Scenario #{index + 1}: #{scenario.description}")
64
51
 
65
- rescue ScriptAborted
66
- self.class.write_log("Aborted")
67
- return false
52
+ # run all the steps within the scenario
53
+ scenario.run_result = run_result
54
+ scenario.run
68
55
 
56
+ puts("\n")
69
57
  end
70
58
  end
71
59
 
72
60
  #
73
- # Returns the log of the last run
61
+ # Defines a new script
74
62
  #
75
- def self.last_log
76
- @last_log ||= ""
77
- end
78
-
79
- #
80
- # Defines a script
63
+ # Diddy::Script.define('Some recipe') do
64
+ # scenario('Putting stuff in a bowl') do
65
+ # step 'Put milk in it'
66
+ # step 'Drink from bowl'
67
+ # end
68
+ # end
81
69
  #
82
- # Diddy::Script.define('Test API') do
83
- # uses ApiSteps
84
- # uses LoginSteps
85
- #
86
- # step 'Do something'
87
- # step 'Do something else'
88
- # end
89
- #
90
- def self.define(scenario, &block)
70
+ def self.define(description, &block)
91
71
  @scripts ||= []
92
72
 
93
- script = self.new(scenario)
73
+ script = self.new(description)
94
74
  script.instance_eval(&block)
95
75
 
96
76
  @scripts << script
97
- end
98
-
99
- #
100
- # Runs all defined scripts. Returns true if and only if all indivudial
101
- # scripts returned true. Otherwise returns false.
102
- #
103
- def self.run_all
104
- # empty log
105
- @last_log = ""
106
-
107
- write_log("[#{Time.now}] Diddy starting to run #{@scripts.size} scripts")
108
-
109
- # Run all scripts and remember their return status
110
- status = @scripts.map do |script|
111
- # run the script
112
- result = script.run
113
-
114
- # concat log of script to general log
115
- write_log(script.log)
116
77
 
117
- result
118
- end
119
-
120
- write_log("[#{Time.now}] Diddy finished running #{@scripts.size} scripts")
121
-
122
- # If one of the scripts returned with "false"; make the entire run
123
- # return false as well
124
- status.include?(false) ? false : true
78
+ script
125
79
  end
126
80
 
127
81
  #
128
- # Only runs given script
82
+ # Returns all the defined scripts
129
83
  #
130
- def self.only_run(scenario)
131
- @scripts.select { |script| script.scenario == scenario }.first.run
132
- end
133
-
134
84
  def self.scripts
135
85
  @scripts
136
86
  end
137
87
 
138
- private
139
-
140
- def self.write_log(message, print = true)
141
- puts(message) if print
142
- self.last_log << "#{message}\n"
143
- end
144
-
145
88
  #
146
- # Runs one step
89
+ # Runs a given script
147
90
  #
148
- def run_step(step)
149
- # run proc on this instance as scope
150
- begin
151
- result = step.run
152
-
153
- rescue Exception => exception
154
- log_step(step, STATE_EXCEPTION)
155
- print_exception(step, exception)
156
- raise ScriptAborted.new
157
- end
158
-
159
- if result
160
- log_step(step, STATE_OK)
161
- else
162
- log_step(step, STATE_FAILED)
163
- raise ScriptAborted.new
164
- end
165
- end
91
+ def self.run(run_result, description, options = {})
92
+ # find script
93
+ script = scripts.find { |script| script.description == description }
94
+ raise CannotFindScriptError.new("Cannot find script #{description}") unless script
166
95
 
167
- #
168
- # Logs step to log
169
- #
170
- def log_step(step, state)
171
- if state == STATE_FAILED || state == STATE_EXCEPTION
172
- print(red(bold("✕ #{step.description}")))
173
- self.class.write_log("✕ #{step.description}", false)
174
-
175
- if state == STATE_EXCEPTION
176
- print(" [EXCEPTION]")
177
- print("\n\n")
178
- self.class.write_log("[EXCEPTION]", false)
179
- end
96
+ # output and run
97
+ if options.any?
98
+ vars = " (" + options.map { |k, v| "#{k}=#{v}" }.join(', ') + ")"
180
99
  else
181
- print(green("✓ #{step.description}"), "\n")
182
- self.class.write_log("✓ #{step.description}", false)
100
+ vars = ""
183
101
  end
184
- end
185
102
 
186
- #
187
- # Prints exception thrown by step, on screen
188
- #
189
- def print_exception(current_step, exception)
190
- # print backtrace
191
- self.class.write_log("- #{exception.message}")
192
- self.class.write_log(" #{exception.backtrace.join("\n ")}")
193
- self.class.write_log("\n")
194
- end
103
+ # print to screen what we are doing
104
+ puts(bold("Running #{description}#{vars}"))
195
105
 
196
- #
197
- # Finds the instance of the steps definition by step name
198
- #
199
- def find_steps_instance_for(description)
200
- @steps_instances.each do |instance|
201
- if instance.class.steps && instance.class.steps.has_key?(description)
202
- return instance
203
- end
204
- end
106
+ # also log to result logger
107
+ run_result.run_script("Running #{description}#{vars}")
205
108
 
206
- nil
207
- end
109
+ # apply the context and run result
110
+ script.context = Context.new(options)
111
+ script.run_result = run_result
208
112
 
209
- def shared_scope
210
- @shared_scope ||= SharedScope.new
113
+ # run the entire script
114
+ script.run
211
115
  end
212
116
 
213
- class ScriptAborted < Exception; end
214
-
117
+ class CannotFindScriptError < StandardError; end
215
118
  end
216
119
  end
@@ -9,10 +9,9 @@ module Diddy
9
9
  end
10
10
 
11
11
  private
12
-
12
+
13
13
  def vars
14
14
  @vars ||= {}
15
15
  end
16
-
17
16
  end
18
17
  end
data/lib/diddy/step.rb CHANGED
@@ -1,7 +1,12 @@
1
1
  # encoding: utf-8
2
2
  module Diddy
3
3
  class Step
4
- attr_accessor :description, :definition, :steps_instance
4
+ STATE_OK = 1
5
+ STATE_FAILED = 2
6
+ STATE_EXCEPTION = 3
7
+ STATE_PENDING = 4
8
+
9
+ attr_accessor :description, :definition, :steps_instance, :run_result
5
10
 
6
11
  #
7
12
  # Initializes step
@@ -14,7 +19,53 @@ module Diddy
14
19
  # Runs the step
15
20
  #
16
21
  def run
17
- steps_instance.instance_eval(&definition)
22
+ # set the sub steps to 0
23
+ steps_instance.sub_steps = []
24
+
25
+ # write to screen
26
+ print(blue(". #{description}"))
27
+
28
+ # and to log
29
+ run_result.run_step(description)
30
+
31
+ # eval the step
32
+ steps_instance.current_step = self
33
+
34
+ # run the step itself
35
+ result = steps_instance.instance_eval(&definition)
36
+
37
+ # check if there were any sub steps
38
+ # if so, the result is dependent of those substeps
39
+ if steps_instance.sub_steps.size > 0
40
+ # check result of all sub_steps
41
+ result = steps_instance.sub_steps.all? { |step| step[:valid] }
42
+
43
+ # also log
44
+ steps_instance.sub_steps.each do |sub_step|
45
+ run_result.run_sub_step(sub_step[:description])
46
+ run_result.set_sub_step_result(sub_step[:valid])
47
+ end
48
+
49
+ # faulty
50
+ print("\n")
51
+ else
52
+ print("\r")
53
+
54
+ if result
55
+ print(green("✓ #{description}"))
56
+ else
57
+ print(red(bold("✕ #{description}")))
58
+ end
59
+ end
60
+
61
+ # log result of step
62
+ run_result.set_step_result(result)
63
+
64
+ # next line
65
+ print("\n")
66
+
67
+ # return the result
68
+ result
18
69
  end
19
70
  end
20
71
  end
data/lib/diddy/steps.rb CHANGED
@@ -1,7 +1,10 @@
1
+ # encoding: utf-8
1
2
  module Diddy
2
3
  class Steps
3
4
  include Helpers
4
5
 
6
+ attr_accessor :sub_steps, :current_step
7
+
5
8
  def initialize(shared_scope)
6
9
  @shared_scope = shared_scope
7
10
  end
@@ -17,6 +20,20 @@ module Diddy
17
20
  # Steps must return TRUE to keep the script running.
18
21
  # When a step returns a FALSE, it will stop the script.
19
22
  #
23
+ # It is also to use sub_steps (not to confuse with dubstep).
24
+ #
25
+ # step('Do something crazy') do
26
+ # sub_step('Eat brains') do
27
+ # a == true
28
+ # end
29
+ #
30
+ # sub_step('Go insane') do
31
+ # b == true
32
+ # end
33
+ # end
34
+ #
35
+ # In the case above, every sub_step needs to return true.
36
+ #
20
37
  def self.step(description, &block)
21
38
  @steps ||= {}
22
39
  @steps[description] = block
@@ -49,5 +66,29 @@ module Diddy
49
66
  def shared
50
67
  @shared_scope
51
68
  end
69
+
70
+ #
71
+ # Adds a sub_step
72
+ #
73
+ def sub_step(description, &block)
74
+ # print pending to screen
75
+ print("\n")
76
+ print(blue(" . #{description}"))
77
+
78
+ # yield the block
79
+ valid = yield(block)
80
+
81
+ # jump back to beginning of line
82
+ print("\r")
83
+
84
+ # print out success or failure
85
+ if valid
86
+ print(green(" ✓ #{description}"))
87
+ else
88
+ print(red(bold(" ✕ #{description}")))
89
+ end
90
+
91
+ self.sub_steps << { description: description, valid: valid }
92
+ end
52
93
  end
53
94
  end
data/lib/diddy.rb CHANGED
@@ -5,6 +5,10 @@ end
5
5
 
6
6
  require 'diddy/helpers'
7
7
  require 'diddy/script'
8
+ require 'diddy/context'
9
+ require 'diddy/scenario'
8
10
  require 'diddy/step'
9
11
  require 'diddy/steps'
12
+ require 'diddy/run_result'
13
+ require 'diddy/run_result_printer'
10
14
  require 'diddy/shared_scope'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: diddy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,10 +10,10 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-02-28 00:00:00.000000000 Z
13
+ date: 2013-10-18 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
- name: term/aniscolor
16
+ name: term/ansicolor
17
17
  requirement: !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
@@ -43,7 +43,11 @@ files:
43
43
  - diddy-0.1.0.gem
44
44
  - diddy.gemspec
45
45
  - lib/diddy.rb
46
+ - lib/diddy/context.rb
46
47
  - lib/diddy/helpers.rb
48
+ - lib/diddy/run_result.rb
49
+ - lib/diddy/run_result_printer.rb
50
+ - lib/diddy/scenario.rb
47
51
  - lib/diddy/script.rb
48
52
  - lib/diddy/shared_scope.rb
49
53
  - lib/diddy/step.rb