rutema 1.3.0 → 2.0.0.pre

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.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/History.txt +204 -194
  3. data/Manifest.txt +15 -48
  4. data/README.md +55 -61
  5. data/bin/rutema +7 -7
  6. data/lib/rutema/application.rb +61 -0
  7. data/lib/rutema/core/configuration.rb +195 -0
  8. data/lib/rutema/core/engine.rb +186 -0
  9. data/lib/rutema/core/framework.rb +28 -0
  10. data/lib/rutema/{objectmodel.rb → core/objectmodel.rb} +43 -48
  11. data/lib/rutema/core/parser.rb +35 -0
  12. data/lib/rutema/core/reporter.rb +105 -0
  13. data/lib/rutema/core/runner.rb +83 -0
  14. data/lib/rutema/elements/minimal.rb +47 -44
  15. data/lib/rutema/parsers/xml.rb +154 -186
  16. data/lib/rutema/version.rb +9 -0
  17. metadata +39 -108
  18. data/README.txt +0 -44
  19. data/Rakefile +0 -30
  20. data/examples/README.md +0 -17
  21. data/examples/config/database.rutema +0 -17
  22. data/examples/config/full.rutema +0 -27
  23. data/examples/config/minimal.rutema +0 -10
  24. data/examples/specs/T001.spec +0 -8
  25. data/examples/specs/T002.spec +0 -8
  26. data/examples/specs/T003.spec +0 -8
  27. data/examples/specs/T004.spec +0 -8
  28. data/examples/specs/T005.spec +0 -10
  29. data/examples/specs/T006.spec +0 -9
  30. data/examples/specs/check.spec +0 -8
  31. data/examples/specs/fail.spec +0 -9
  32. data/examples/specs/include.scenario +0 -5
  33. data/examples/specs/rutema.spec +0 -10
  34. data/examples/specs/setup.spec +0 -8
  35. data/examples/specs/teardown.spec +0 -8
  36. data/lib/rutema/configuration.rb +0 -173
  37. data/lib/rutema/models/activerecord.rb +0 -159
  38. data/lib/rutema/models/base.rb +0 -5
  39. data/lib/rutema/parsers/base.rb +0 -45
  40. data/lib/rutema/rake.rb +0 -62
  41. data/lib/rutema/reporters/activerecord.rb +0 -82
  42. data/lib/rutema/reporters/base.rb +0 -23
  43. data/lib/rutema/reporters/email.rb +0 -84
  44. data/lib/rutema/reporters/text.rb +0 -77
  45. data/lib/rutema/runners/default.rb +0 -157
  46. data/lib/rutema/runners/step.rb +0 -23
  47. data/lib/rutema/system.rb +0 -302
  48. data/test/data/duplicate_name.spec +0 -8
  49. data/test/data/no_title.spec +0 -5
  50. data/test/data/sample.spec +0 -8
  51. data/test/data/test_identifiers.rutema +0 -7
  52. data/test/test_activerecord.rb +0 -0
  53. data/test/test_configuration.rb +0 -43
  54. data/test/test_objectmodel.rb +0 -82
  55. data/test/test_parsers.rb +0 -131
  56. data/test/test_reporters.rb +0 -115
  57. data/test/test_runners.rb +0 -70
  58. data/test/test_system.rb +0 -45
@@ -0,0 +1,28 @@
1
+ module Rutema
2
+ module Messaging
3
+ def error identifier,message
4
+ message(:test=>identifier,:error=>message)
5
+ end
6
+ def message message
7
+ msg=message
8
+ if message.is_a?(String)
9
+ msg={:message=>message,:timestamp=>Time.now}
10
+ elsif message.is_a?(Hash)
11
+ msg[:timestamp]=Time.now
12
+ end
13
+ @queue.push(msg)
14
+ end
15
+ end
16
+ #Generic error class for errors in the engine
17
+ class RutemaError<RuntimeError
18
+ end
19
+ #Is raised when an error is found in a specification
20
+ class ParserError<RutemaError
21
+ end
22
+ #Is raised on an unexpected error during execution
23
+ class RunnerError<RutemaError
24
+ end
25
+ #Errors in reporters should use this class
26
+ class ReportError<RutemaError
27
+ end
28
+ end
@@ -1,5 +1,4 @@
1
- # Copyright (c) 2007-2010 Vassilis Rizopoulos. All rights reserved.
2
- $:.unshift File.join(File.dirname(__FILE__),"..")
1
+ # Copyright (c) 2007-2015 Vassilis Rizopoulos. All rights reserved.
3
2
  require 'patir/command'
4
3
 
5
4
  module Rutema
@@ -7,9 +6,10 @@ module Rutema
7
6
  #arbitrarily add attributes to a class and then have
8
7
  #the accessor methods for these attributes appear automagically.
9
8
  #
10
- #It will also add a has_attribute? method to query if an attribute is part of the object or not.
9
+ #It will also add a has_attribute? method to query if _attribute_ is part of the object or not.
11
10
  module SpecificationElement
12
- #adds an attribute to the class with the given __value__. __symbol__ can be a Symbol or a String, the rest are silently ignored
11
+ #adds an attribute to the class with the given __value__. __symbol__ can be a Symbol or a String,
12
+ #the rest are silently ignored
13
13
  def attribute symbol,value
14
14
  @attributes||=Hash.new
15
15
  case symbol
@@ -35,12 +35,24 @@ module Rutema
35
35
  super(symbol,*args)
36
36
  end
37
37
  end
38
+
39
+ def respond_to? symbol,include_all
40
+ @attributes||=Hash.new
41
+ key=symbol.id2name.chomp('?').chomp('=').sub(/^has_/,"")
42
+ if @attributes.has_key?(:"#{key}")
43
+ return true
44
+ else
45
+ super(symbol,include_all)
46
+ end
47
+ end
38
48
  end
39
- #A TestSpecification encompasses all elements required to run a test, the builds used, the scenario to run,
49
+ #A Rutema::Specification encompasses all elements required to run a test, the builds used, the scenario to run,
40
50
  #together with a textual description and information that aids in tracing the test back to the requirements.
41
- class TestSpecification
51
+ class Specification
42
52
  include SpecificationElement
43
- attr_accessor :scenario,:requirements
53
+ attr_accessor :scenario
54
+ #Expects a Hash of parameters
55
+ #
44
56
  #Following keys have meaning in initialization:
45
57
  #
46
58
  #:name - the name of the testcase. Should uniquely identify the testcase
@@ -51,15 +63,12 @@ module Rutema
51
63
  #
52
64
  #:description - a full textual description for the testcase. To be used in reports and documents
53
65
  #
54
- #:scenario - An instance of TestScenario
55
- #
56
- #:requirements - An Array of String. The idea is that the strings can lead you back to the requirements specification that is tested here.
66
+ #:scenario - An instance of Rutema::Scenario
57
67
  #
58
68
  #:version - The version of this specification
59
69
  #
60
70
  #Default values are empty strings and arrays. (scenario is nil)
61
- def initialize *args
62
- params=args[0] if args
71
+ def initialize params
63
72
  begin
64
73
  @attributes=params
65
74
  end if params
@@ -68,59 +77,44 @@ module Rutema
68
77
  @attributes[:title]||=""
69
78
  @attributes[:filename]||=""
70
79
  @attributes[:description]||=""
71
- @scenario=TestScenario.new(@attributes[:version])
72
- @requirements||=Array.new
80
+ @scenario=@attributes[:scenario]
73
81
  end
74
82
  def to_s#:nodoc:
75
83
  return "#{@attributes[:name]} - #{@attributes[:title]}"
76
84
  end
77
85
  end
78
- #A TestScenario is a sequence of TestStep instances.
86
+ #A Rutema::Scenario is a sequence of Rutema::Step instances.
79
87
  #
80
- #TestStep instances are run in the definition sequence and the scenario
88
+ #Rutema::Step instances are run in the definition sequence and the scenario
81
89
  #is succesfull when all steps are succesfull.
82
90
  #
83
91
  #From the execution point of view each step is either succesfull or failed and it depends on
84
92
  #the exit code of the step's command.
85
93
  #
86
94
  #Failure in a step results in the interruption of execution and the report of the errors.
87
- class TestScenario
95
+ class Scenario
88
96
  include SpecificationElement
89
97
  attr_reader :steps
90
98
 
91
- def initialize version=nil
99
+ def initialize steps
92
100
  @attributes=Hash.new
93
- #attended is off by default
94
- @attributes[:attended]=false
95
- @version=version
96
- @steps=Array.new
101
+ @steps=steps
102
+ @steps||=Array.new
97
103
  end
98
-
99
- def attended?
100
- ret=@attributes[:attended]
101
- @steps.each do |step|
102
- ret=true if step.attended?
103
- end
104
- return ret
105
- end
106
-
104
+ #Adds a step at the end of the step sequence
107
105
  def add_step step
108
106
  @steps<<step
109
- @attended=true if step.attended?
110
107
  end
111
-
108
+ #Overwrites the step sequence
112
109
  def steps= array_of_steps
113
110
  @steps=array_of_steps
114
- @steps.each do |step|
115
- @attributes[:attended]=true if step.attended?
116
- end
117
111
  end
118
112
  end
119
- #Represents a step in a TestScenario.
113
+ #Represents a step in a Scenario.
120
114
  #
121
- #Each TestStep can have text and a command associated with it.
115
+ #Each Rutema::Step can have text and a command associated with it.
122
116
  #
123
- #TestStep standard attributes are.
117
+ #Step standard attributes are.
124
118
  #
125
119
  #attended - the step can only run in attended mode, it requires user input.
126
120
  #
@@ -136,20 +130,20 @@ module Rutema
136
130
  #
137
131
  #==Dynamic behaviour
138
132
  #
139
- #A TestStep can be queried dynamicaly about the attributes it posesses:
133
+ #A Rutema::Step can be queried dynamicaly about the attributes it posesses:
140
134
  # step.has_script? - will return true if script is step's attribute.
141
135
  #Attribute's are mostly assigned by the parser, i.e. the Rutema::BaseXMLParser from the XML element
142
136
  # <test script="some_script"/>
143
- #will create a TestStep instance with step_type=="test" and script="some_script". In this case
137
+ #will create a Step instance with step_type=="test" and script="some_script". In this case
144
138
  #
145
139
  # step.has_script? returns true
146
140
  # step.script returns "some_script"
147
141
  #
148
- #Just like an OpenStruct, TestStep attributes will be created by direct assignment:
142
+ #Just like an OpenStruct, Step attributes will be created by direct assignment:
149
143
  # step.script="some_script" creates the script attribute if it does not exist.
150
144
  #
151
145
  #See Rutema::SpecificationElement for the implementation details.
152
- class TestStep
146
+ class Step
153
147
  include SpecificationElement
154
148
  include Patir::Command
155
149
 
@@ -158,8 +152,6 @@ module Rutema
158
152
  @attributes=Hash.new
159
153
  #ignore is off by default
160
154
  @attributes[:ignore]=false
161
- #attended is off by default
162
- @attributes[:attended]=false
163
155
  #assign
164
156
  @attributes[:cmd]=cmd if cmd
165
157
  @attributes[:text]=txt
@@ -206,9 +198,12 @@ module Rutema
206
198
  end
207
199
  def to_s#:nodoc:
208
200
  param=""
209
- param=" - #{self.cmd.to_s}" if self.has_cmd?
210
- msg="#{self.number} - #{self.step_type}#{param} - #{self.status}"
211
- msg<<" in #{self.included_in}" if self.has_included_in?
201
+ if self.has_cmd?
202
+ msg="#{self.number} - #{self.cmd.to_s}"
203
+ else
204
+ msg="#{self.number} - #{self.name}"
205
+ end
206
+ msg<<" in #{self.included_in}" if self.has_included_in?
212
207
  return msg
213
208
  end
214
209
  end
@@ -0,0 +1,35 @@
1
+ # Copyright (c) 2007-2015 Vassilis Rizopoulos. All rights reserved.
2
+ require_relative 'framework'
3
+
4
+ module Rutema
5
+ module Parsers
6
+ #Base class that bombs out when used.
7
+ #
8
+ #Derive your parser class from this class and implement parse_specification and validate_configuration
9
+ class SpecificationParser
10
+ attr_reader :configuration
11
+ def initialize configuration
12
+ @configuration=configuration
13
+ @configuration||={}
14
+ validate_configuration
15
+ end
16
+ #parses a specification
17
+ def parse_specification param
18
+ raise ParserError,"not implemented. You should derive a parser implementation from SpecificationParser!"
19
+ end
20
+ #parses the setup script. By default calls parse_specification
21
+ def parse_setup param
22
+ parse_specification(param)
23
+ end
24
+ #parses the teardown script. By default calls parse_specification
25
+ def parse_teardown param
26
+ parse_specification(param)
27
+ end
28
+ #The parser stores it's configuration in @configuration
29
+ #
30
+ #To avoid validating the configuration in element_* methods repeatedly, do all configuration validation here
31
+ def validate_configuration
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,105 @@
1
+ # Copyright (c) 2007-2015 Vassilis Rizopoulos. All rights reserved.
2
+ module Rutema
3
+ #Rutema supports two kinds of reporters.
4
+ #
5
+ #Block (from en bloc) reporters receive data via the report() method at the end of a Rutema run
6
+ #while event reporters receive events continuously during a run via the update() method
7
+ #
8
+ #Nothing prevents you from creating a class that implements both behaviours
9
+ module Reporters
10
+ class BlockReporter
11
+ def initialize configuration,dispatcher
12
+ @configuration=configuration
13
+ end
14
+ def report specifications,states,errors
15
+ end
16
+ end
17
+ class EventReporter
18
+ def initialize configuration,dispatcher
19
+ @configuration=configuration
20
+ @queue=dispatcher.subscribe(self.object_id)
21
+ end
22
+
23
+ def run!
24
+ @thread=Thread.new do
25
+ while true do
26
+ if @queue.size>0
27
+ data=@queue.pop
28
+ update(data) if data
29
+ end
30
+ sleep 0.1
31
+ end
32
+ end
33
+ end
34
+
35
+ def update data
36
+ end
37
+
38
+ def exit
39
+ if @thread
40
+ while @queue.size>0 do
41
+ sleep 0.1
42
+ end
43
+ Thread.kill(@thread)
44
+ end
45
+ end
46
+ end
47
+
48
+ class Collector<EventReporter
49
+ attr_reader :errors,:states
50
+ def initialize params,dispatcher
51
+ super(params,dispatcher)
52
+ @errors=[]
53
+ @states={}
54
+ end
55
+
56
+ def update data
57
+ if data[:error]
58
+ @errors<<data
59
+ elsif data[:test] && data['status']
60
+ @states[data[:test]]||=[]
61
+ @states[data[:test]]<<data
62
+ end
63
+ end
64
+ end
65
+
66
+ class Console<EventReporter
67
+ def initialize configuration,dispatcher
68
+ super(configuration,dispatcher)
69
+ @silent=configuration.reporters.fetch(self.class,{})["silent"]
70
+ end
71
+ def update data
72
+ if data[:error]
73
+ puts ">ERROR: #{data[:error]}"
74
+ elsif data[:test]
75
+ if data["phase"]
76
+ puts ">#{data["phase"]} #{data[:test]}" unless @silent
77
+ elsif data[:message]
78
+ puts ">#{data[:test]} #{data[:message]}" unless @silent
79
+ elsif data["status"]==:error
80
+ puts ">FATAL: #{data[:test]}(#{data["number"]}) failed"
81
+ puts data.fetch("out","")
82
+ puts data.fetch("error","")
83
+ end
84
+ elsif data[:message]
85
+ puts ">#{data[:message]}" unless @silent
86
+ end
87
+ end
88
+ end
89
+
90
+ class Summary<BlockReporter
91
+ def initialize configuration,dispatcher
92
+ super(configuration,dispatcher)
93
+ @silent=configuration.reporters.fetch(self.class,{})["silent"]
94
+ end
95
+ def report specs,states,errors
96
+ failures=0
97
+ states.each do |k,v|
98
+ failures+=1 if v.last['status']==:error
99
+ end
100
+ puts "#{errors.size} errors. #{states.size} test cases executed. #{failures} failed" unless @silent
101
+ return failures
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,83 @@
1
+ # Copyright (c) 2007-2015 Vassilis Rizopoulos. All rights reserved.
2
+
3
+ require_relative "framework"
4
+
5
+ module Rutema
6
+ module Runners
7
+ class Default
8
+ include Rutema::Messaging
9
+ attr_reader :context
10
+ attr_accessor :setup,:teardown
11
+ def initialize context,queue
12
+ @setup=nil
13
+ @teardown=nil
14
+ @context=context || Hash.new
15
+ @queue = queue
16
+ @number_of_runs=0
17
+ end
18
+
19
+ def run spec
20
+ state={'start_time'=>Time.now, "sequence_id"=>@number_of_runs,:test=>spec.name}
21
+ steps=[]
22
+ status=:success
23
+ message(:test=>spec.name,'phase'=>'started')
24
+ if @setup
25
+ message(:test=>spec.name,'phase'=>'setup')
26
+ executed_steps,status=run_scenario("_setup_",@setup.scenario,@context)
27
+ steps+=executed_steps
28
+ end
29
+ if status!=:error
30
+ message(:test=>spec.name,'phase'=>'running')
31
+ executed_steps,status=run_scenario(spec.name,spec.scenario,@context)
32
+ steps+=executed_steps
33
+ else
34
+ message(:test=>spec.name,'number'=>0,'status'=>:error,'out'=>"Setup failed",'err'=>"",'duration'=>0)
35
+ end
36
+ state['status']=status
37
+ if @teardown
38
+ message(:test=>spec.name,'phase'=>'teardown')
39
+ executed_steps,status=run_scenario("_teardown_",@teardown.scenario,@context)
40
+ end
41
+ message(:test=>spec.name,'phase'=>'finished')
42
+ state["stop_time"]=Time.now
43
+ state['steps']=steps
44
+ @number_of_runs+=1
45
+ return state
46
+ end
47
+
48
+ private
49
+ def run_scenario name,scenario,meta
50
+ executed_steps=[]
51
+ status=:warning
52
+ begin
53
+ stps=scenario.steps
54
+ if stps.empty?
55
+ error(name,"Scenario #{name} contains no steps")
56
+ status=:error
57
+ else
58
+ stps.each do |s|
59
+ message(:test=>name,:message=>s.to_s)
60
+ executed_steps<<run_step(s,meta)
61
+ message(:test=>name,'number'=>s.number,'status'=>s.status,'out'=>s.output,'err'=>s.error,'duration'=>s.exec_time)
62
+ status=s.status
63
+ break if :error==s.status
64
+ end
65
+ end
66
+ rescue
67
+ error(name,$!.message)
68
+ status=:error
69
+ end
70
+ return executed_steps,status
71
+ end
72
+ def run_step step,meta
73
+ if step.has_cmd? && step.cmd.respond_to?(:run)
74
+ step.cmd.run(meta)
75
+ else
76
+ message("No command associated with step '#{step.step_type}'. Step number is #{step.number}")
77
+ end
78
+ step.status=:success if step.status==:error && step.ignore?
79
+ return step
80
+ end
81
+ end
82
+ end
83
+ end
@@ -1,45 +1,48 @@
1
- # Copyright (c) 2007-2011 Vassilis Rizopoulos. All rights reserved.
2
- $:.unshift File.join(File.dirname(__FILE__),'..','..')
3
- module Rutema
4
- #The Elements module provides the namespace for the various modules adding parser functionality
5
- module Elements
6
- #Minimal offers a minimal(chic) set of elements for use in specifications
7
- #
8
- #These are:
9
- # echo
10
- # command
11
- # prompt
12
- module Minimal
13
- #echo prints a message on the screen:
14
- # <echo text="A meaningful message"/>
15
- # <echo>A meaningful message</echo>
16
- def element_echo step
17
- step.cmd=Patir::RubyCommand.new("echo"){|cmd| cmd.error="";cmd.output="#{step.text}";$stdout.puts(cmd.output) ;:success}
18
- end
19
- #prompt asks the user a yes/no question. Answering yes means the step is succesful.
20
- # <prompt text="Do you want fries with that?"/>
21
- #
22
- #A prompt element automatically makes a specification "attended"
23
- def element_prompt step
24
- step.attended=true
25
- step.cmd=Patir::RubyCommand.new("prompt") do |cmd|
26
- cmd.output=""
27
- cmd.error=""
28
- if HighLine.new.agree("#{step.text}")
29
- step.output="y"
30
- else
31
- raise "n"
32
- end#if
33
- end#do rubycommand
34
- end
35
- #command executes a shell command
36
- # <command cmd="useful_command.exe with parameters", working_directory="some/directory"/>
37
- def element_command step
38
- raise ParserError,"missing required attribute cmd in #{step}" unless step.has_cmd?
39
- wd=Dir.pwd
40
- wd=step.working_directory if step.has_working_directory?
41
- step.cmd=Patir::ShellCommand.new(:cmd=>step.cmd,:working_directory=>File.expand_path(wd))
42
- end
43
- end
44
- end
1
+ # Copyright (c) 2007-2015 Vassilis Rizopoulos. All rights reserved.
2
+ require 'highline'
3
+ module Rutema
4
+ #The Elements module provides the namespace for the various modules adding parser functionality
5
+ module Elements
6
+ #Minimal offers a minimal(chic) set of elements for use in specifications
7
+ #
8
+ #These are:
9
+ # echo
10
+ # command
11
+ # prompt
12
+ module Minimal
13
+ #echo prints a message on the screen:
14
+ # <echo text="A meaningful message"/>
15
+ # <echo>A meaningful message</echo>
16
+ def element_echo step
17
+ step.cmd=Patir::RubyCommand.new("echo"){|cmd| cmd.error="";cmd.output="#{step.text}";$stdout.puts(cmd.output) ;:success}
18
+ return step
19
+ end
20
+ #prompt asks the user a yes/no question. Answering yes means the step is succesful.
21
+ # <prompt text="Do you want fries with that?"/>
22
+ #
23
+ #A prompt element automatically makes a specification "attended"
24
+ def element_prompt step
25
+ step.attended=true
26
+ step.cmd=Patir::RubyCommand.new("prompt") do |cmd|
27
+ cmd.output=""
28
+ cmd.error=""
29
+ if HighLine.new.agree("#{step.text}")
30
+ step.output="y"
31
+ else
32
+ raise "n"
33
+ end#if
34
+ end#do rubycommand
35
+ return step
36
+ end
37
+ #command executes a shell command
38
+ # <command cmd="useful_command.exe with parameters", working_directory="some/directory"/>
39
+ def element_command step
40
+ raise ParserError,"missing required attribute cmd in #{step}" unless step.has_cmd?
41
+ wd=Dir.pwd
42
+ wd=step.working_directory if step.has_working_directory?
43
+ step.cmd=Patir::ShellCommand.new(:cmd=>step.cmd,:working_directory=>File.expand_path(wd))
44
+ return step
45
+ end
46
+ end
47
+ end
45
48
  end