rutema 1.3.0 → 2.0.0.pre

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