diddy 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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