rutema 1.1.3 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/.gemtest +0 -0
  2. data/History.txt +5 -0
  3. data/Manifest.txt +28 -23
  4. data/README.md +91 -0
  5. data/README.txt +2 -10
  6. data/Rakefile +6 -9
  7. data/bin/rutema +9 -0
  8. data/bin/rutemax +2 -0
  9. data/lib/rutema/configuration.rb +7 -8
  10. data/lib/rutema/elements/minimal.rb +45 -0
  11. data/lib/rutema/gems.rb +3 -3
  12. data/lib/rutema/models/activerecord.rb +164 -0
  13. data/lib/rutema/models/base.rb +5 -0
  14. data/lib/rutema/{specification.rb → objectmodel.rb} +4 -10
  15. data/lib/rutema/parsers/base.rb +29 -0
  16. data/lib/rutema/parsers/xml.rb +186 -0
  17. data/lib/rutema/reporters/activerecord.rb +9 -18
  18. data/lib/rutema/{reporter.rb → reporters/base.rb} +3 -7
  19. data/lib/rutema/reporters/email.rb +2 -5
  20. data/lib/rutema/reporters/text.rb +2 -2
  21. data/lib/rutema/reporters/yaml.rb +1 -2
  22. data/lib/rutema/runners/default.rb +157 -0
  23. data/lib/rutema/runners/step.rb +23 -0
  24. data/lib/rutema/system.rb +15 -420
  25. data/test/distro_test/config/full.rutema +2 -0
  26. data/test/distro_test/specs/check.spec +8 -0
  27. data/test/distro_test/specs/duplicate_name.spec +8 -0
  28. data/test/distro_test/specs/fail.spec +9 -0
  29. data/test/distro_test/specs/setup.spec +8 -0
  30. data/test/distro_test/specs/teardown.spec +8 -0
  31. data/test/rutema.rutema +1 -1
  32. data/test/rutema.spec +5 -5
  33. data/test/test_activerecord.rb +0 -0
  34. data/test/test_configuration.rb +7 -9
  35. data/test/test_couchdb.rb +14 -0
  36. data/test/{test_specification.rb → test_objectmodel.rb} +8 -11
  37. data/test/test_parsers.rb +136 -0
  38. data/test/test_rake.rb +2 -3
  39. data/test/{test_reporter.rb → test_reporters.rb} +0 -0
  40. data/test/test_runners.rb +72 -0
  41. data/test/test_system.rb +3 -166
  42. metadata +57 -62
  43. data/bin/rutema_upgrader +0 -81
  44. data/distro_test.sh +0 -6
  45. data/lib/rutema/model.rb +0 -231
  46. data/lib/rutema/reporters/couchdb.rb +0 -62
  47. data/lib/rutema/reporters/standard_reporters.rb +0 -7
  48. data/selftest.sh +0 -2
  49. data/test/data/sample09.db +0 -0
  50. data/test/migration.spec +0 -9
  51. data/test/test_model.rb +0 -62
@@ -0,0 +1,5 @@
1
+ module Rutema
2
+ #Exception occuring when connecting to a database
3
+ class ConnectionError<RuntimeError
4
+ end
5
+ end
@@ -1,6 +1,5 @@
1
1
  # Copyright (c) 2007-2010 Vassilis Rizopoulos. All rights reserved.
2
2
  $:.unshift File.join(File.dirname(__FILE__),"..")
3
- require 'rutema/reporters/standard_reporters'
4
3
  require 'patir/command'
5
4
 
6
5
  module Rutema
@@ -37,8 +36,6 @@ module Rutema
37
36
  end
38
37
  end
39
38
  end
40
-
41
-
42
39
  #A TestSpecification encompasses all elements required to run a test, the builds used, the scenario to run,
43
40
  #together with a textual description and information that aids in tracing the test back to the requirements.
44
41
  class TestSpecification
@@ -74,12 +71,10 @@ module Rutema
74
71
  @scenario=TestScenario.new(@attributes[:version])
75
72
  @requirements||=Array.new
76
73
  end
77
-
78
- def to_s
74
+ def to_s#:nodoc:
79
75
  return "#{@attributes[:name]} - #{@attributes[:title]}"
80
76
  end
81
77
  end
82
-
83
78
  #A TestScenario is a sequence of TestStep instances.
84
79
  #
85
80
  #TestStep instances are run in the definition sequence and the scenario
@@ -121,7 +116,6 @@ module Rutema
121
116
  end
122
117
  end
123
118
  end
124
-
125
119
  #Represents a step in a TestScenario.
126
120
  #
127
121
  #Each TestStep can have text and a command associated with it.
@@ -210,7 +204,7 @@ module Rutema
210
204
  param=" - #{self.cmd.to_s}" if self.has_cmd?
211
205
  return "#{@attributes[:step_type]}#{param}"
212
206
  end
213
- def to_s
207
+ def to_s#:nodoc:
214
208
  param=""
215
209
  param=" - #{self.cmd.to_s}" if self.has_cmd?
216
210
  msg="#{self.number} - #{self.step_type}#{param} - #{self.status}"
@@ -222,13 +216,13 @@ module Rutema
222
216
  end
223
217
 
224
218
  class Patir::ShellCommand
225
- def to_s
219
+ def to_s#:nodoc:
226
220
  return @command
227
221
  end
228
222
  end
229
223
 
230
224
  class Patir::RubyCommand
231
- def to_s
225
+ def to_s#:nodoc:
232
226
  return @name
233
227
  end
234
228
  end
@@ -0,0 +1,29 @@
1
+ # Copyright (c) 2007-2011 Vassilis Rizopoulos. All rights reserved.
2
+ $:.unshift File.join(File.dirname(__FILE__),'..','..')
3
+
4
+ module Rutema
5
+ #Is raised when an error is found in a specification
6
+ class ParserError<RuntimeError
7
+ end
8
+ #Base class that bombs out when used.
9
+ #
10
+ #Initialze expects a hash and as a base implementation assigns :logger as the internal logger.
11
+ #
12
+ #By default the internal logger will log to the console if no logger is provided.
13
+ class SpecificationParser
14
+ attr_reader :configuration
15
+ def initialize params
16
+ @configuration=params
17
+ @logger.warn("No system configuration provided to the parser") unless @configuration
18
+ @logger=@configuration[:logger]
19
+ unless @logger
20
+ @logger=Patir.setup_logger
21
+ @configuration[:logger]=@logger
22
+ end
23
+ end
24
+
25
+ def parse_specification param
26
+ raise ParserError,"not implemented. You should derive a parser implementation from SpecificationParser!"
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,186 @@
1
+ # Copyright (c) 2007-2011 Vassilis Rizopoulos. All rights reserved.
2
+ $:.unshift File.join(File.dirname(__FILE__),'..','..')
3
+ require 'rexml/document'
4
+ require 'patir/command'
5
+ require 'rutema/objectmodel'
6
+ require 'rutema/parsers/base'
7
+ require 'rutema/elements/minimal'
8
+
9
+ module Rutema
10
+ #BaseXMLParser encapsulates all the XML parsing code
11
+ class BaseXMLParser<SpecificationParser
12
+ ELEM_SPEC="specification"
13
+ ELEM_DESC="specification/description"
14
+ ELEM_TITLE="specification/title"
15
+ ELEM_SCENARIO="specification/scenario"
16
+ ELEM_REQ="requirement"
17
+ #Parses __param__ and returns the Rutema::TestSpecification instance
18
+ #
19
+ #param can be the filename of the specification or the contents of that file.
20
+ #
21
+ #Will throw ParserError if something goes wrong
22
+ def parse_specification param
23
+ @logger.debug("Loading #{param}")
24
+ begin
25
+ if File.exists?(param)
26
+ #read the file
27
+ txt=File.read(param)
28
+ filename=File.expand_path(param)
29
+ else
30
+ filename=Dir.pwd
31
+ #try to parse the parameter
32
+ txt=param
33
+ end
34
+ spec=parse_case(txt,filename)
35
+ raise "Missing required attribute 'name' in specification element" unless spec.has_name? && !spec.name.empty?
36
+ return spec
37
+ rescue
38
+ @logger.debug($!)
39
+ raise ParserError,"Error loading #{param}: #{$!.message}"
40
+ end
41
+ end
42
+
43
+ private
44
+ #Parses the XML specification of a testcase and creates the corresponding TestSpecification instance
45
+ def parse_case xmltxt,filename
46
+ #the testspec to return
47
+ spec=TestSpecification.new
48
+ #read the test spec
49
+ xmldoc=REXML::Document.new( xmltxt )
50
+ #validate it
51
+ validate_case(xmldoc)
52
+ #parse it
53
+ el=xmldoc.elements[ELEM_SPEC]
54
+ xmldoc.root.attributes.each do |attr,value|
55
+ add_attribute(spec,attr,value)
56
+ end
57
+ #get the title
58
+ spec.title=xmldoc.elements[ELEM_TITLE].text
59
+ spec.title||=""
60
+ spec.title.strip!
61
+ #get the description
62
+ #strip line feeds, cariage returns and remove all tabs
63
+ spec.description=xmldoc.elements[ELEM_DESC].text
64
+ spec.description||=""
65
+ begin
66
+ spec.description.strip!
67
+ spec.description.gsub!(/\t/,'')
68
+ end unless spec.description.empty?
69
+ #get the requirements
70
+ reqs=el.elements.select{|e| e.name==ELEM_REQ}
71
+ reqs.collect!{|r| r.attributes["name"]}
72
+ spec.requirements=reqs
73
+ #Get the scenario
74
+ Dir.chdir(File.dirname(filename)) do
75
+ spec.scenario=parse_scenario(xmldoc.elements[ELEM_SCENARIO].to_s) if xmldoc.elements[ELEM_SCENARIO]
76
+ end
77
+ spec.filename=filename
78
+ return spec
79
+ end
80
+ #Validates the XML file from our point of view.
81
+ #
82
+ #Checks for the existence of ELEM_SPEC, ELEM_DESC and ELEM_TITLE and raises ParserError if they're missing.
83
+ def validate_case xmldoc
84
+ raise ParserError,"missing #{ELEM_SPEC} element" unless xmldoc.elements[ELEM_SPEC]
85
+ raise ParserError,"missing #{ELEM_DESC} element" unless xmldoc.elements[ELEM_DESC]
86
+ raise ParserError,"missing #{ELEM_TITLE} element" unless xmldoc.elements[ELEM_TITLE]
87
+ end
88
+
89
+ #Parses the scenario XML element and returns the Rutema::TestScenario instance
90
+ def parse_scenario xmltxt
91
+ @logger.debug("Parsing scenario from #{xmltxt}")
92
+ scenario=Rutema::TestScenario.new
93
+ xmldoc=REXML::Document.new( xmltxt )
94
+ xmldoc.root.attributes.each do |attr,value|
95
+ add_attribute(scenario,attr,value)
96
+ end
97
+ number=0
98
+ xmldoc.root.elements.each do |el|
99
+ step=parse_step(el.to_s)
100
+ if step.step_type=="include_scenario"
101
+ included_scenario=include_scenario(step)
102
+ included_scenario.steps.each do |st|
103
+ @logger.debug("Adding included step #{st}")
104
+ number+=1
105
+ st.number=number
106
+ st.included_in=step.file
107
+ scenario.add_step(st)
108
+ end
109
+ else
110
+ number+=1
111
+ step.number=number
112
+ scenario.add_step(step)
113
+ end
114
+ end
115
+ return scenario
116
+ end
117
+
118
+ #Parses xml and returns the Rutema::TestStep instance
119
+ def parse_step xmltxt
120
+ xmldoc=REXML::Document.new( xmltxt )
121
+ #any step element
122
+ step=Rutema::TestStep.new()
123
+ step.ignore=false
124
+ xmldoc.root.attributes.each do |attr,value|
125
+ add_attribute(step,attr,value)
126
+ end
127
+ step.text=xmldoc.root.text.strip if xmldoc.root.text
128
+ step.step_type=xmldoc.root.name
129
+ return step
130
+ end
131
+
132
+ def add_attribute element,attr,value
133
+ if boolean?(value)
134
+ element.attribute(attr,eval(value))
135
+ else
136
+ element.attribute(attr,value)
137
+ end
138
+ end
139
+
140
+ def boolean? attribute_value
141
+ return true if attribute_value=="true" || attribute_value=="false"
142
+ return false
143
+ end
144
+
145
+ #handles <include_scenario> elements, adding the steps to the current scenario
146
+ def include_scenario step
147
+ @logger.debug("Including file from #{step}")
148
+ raise ParserError,"missing required attribute file in #{step}" unless step.has_file?
149
+ raise ParserError,"Cannot find #{File.expand_path(step.file)}" unless File.exists?(File.expand_path(step.file))
150
+ #Load the scenario
151
+ step.file=File.expand_path(step.file)
152
+ include_content=File.read(step.file)
153
+ @logger.debug(include_content)
154
+ return parse_scenario(include_content)
155
+ end
156
+ end
157
+ #The ExtensibleXMLParser allows you to easily add methods to handle specification elements.
158
+ #
159
+ #A method element_foo(step) allows you to add behaviour for foo scenario elements.
160
+ #
161
+ #The method will receive a Rutema::TestStep instance.
162
+ class ExtensibleXMLParser<BaseXMLParser
163
+ def parse_specification param
164
+ spec = super(param)
165
+ #change into the directory the spec is in to handle relative paths correctly
166
+ Dir.chdir(File.dirname(File.expand_path(spec.filename))) do |path|
167
+ #iterate through the steps
168
+ spec.scenario.steps.each do |step|
169
+ #do we have a method to handle the element?
170
+ if respond_to?(:"element_#{step.step_type}")
171
+ begin
172
+ self.send(:"element_#{step.step_type}",step)
173
+ rescue
174
+ raise ParserError, $!.message
175
+ end
176
+ end
177
+ end
178
+ end
179
+ return spec
180
+ end
181
+ end
182
+ #MinimalXMLParser offers three runnable steps in the scenarios as defined in Rutema::Elements::Minimal
183
+ class MinimalXMLParser<ExtensibleXMLParser
184
+ include Rutema::Elements::Minimal
185
+ end
186
+ end
@@ -1,15 +1,11 @@
1
1
  # Copyright (c) 2007-2010 Vassilis Rizopoulos. All rights reserved.
2
2
  $:.unshift File.join(File.dirname(__FILE__),"..","..")
3
3
  require 'yaml'
4
- require 'rutema/reporter'
5
- require 'rutema/model'
4
+ require 'rutema/models/activerecord'
6
5
 
7
6
  module Rutema
8
7
  #The ActiveRecordReporter will store the results of a test run in a database using ActiveRecord.
9
- #
10
- #The DBMSs supported are dependent on the platform: either SQLite3 (MRI) or h2 (jruby)
11
8
  class ActiveRecordReporter
12
- include ActiveRecord
13
9
  #The required keys in this reporter's configuration are:
14
10
  # :db - the database configuration. A Hash with the DB adapter information
15
11
  # :db=>{:database=>"sample.rb"}
@@ -18,27 +14,26 @@ module Rutema
18
14
  @logger||=Patir.setup_logger
19
15
  database_configuration = definition[:db]
20
16
  raise "No database configuration defined, missing :db configuration key." unless database_configuration
21
- ActiveRecord.connect(database_configuration,@logger)
17
+ Rutema::ActiveRecord.connect(database_configuration,@logger)
22
18
  @logger.info("Reporter #{self.to_s} registered")
23
19
  end
24
-
25
- #We get all the data for a Rutema::ActiveRecord::Model::Run entry in here.
20
+ #We get all the data for a Rutema::ActiveRecord::Run entry in here.
26
21
  #
27
- #If the configuration is given and there is a context defined, this will be YAML-dumped into Rutema::ActiveRecord::Model::Run#context
22
+ #If the configuration is given and there is a context defined, this will be YAML-dumped into Rutema::ActiveRecord::Run#context
28
23
  def report specifications,runner_states,parse_errors,configuration
29
- run_entry=ActiveRecord::Model::Run.new
24
+ run_entry=Rutema::ActiveRecord::Run.new
30
25
  if configuration && configuration.context
31
26
  run_entry.context=configuration.context
32
27
  end
33
28
  parse_errors.each do |pe|
34
- er=ActiveRecord::Model::ParseError.new()
29
+ er=Rutema::ActiveRecord::ParseError.new()
35
30
  er.filename=pe[:filename]
36
31
  er.error=pe[:error]
37
32
  run_entry.parse_errors<<er
38
33
  end
39
34
  runner_states.compact!
40
35
  runner_states.each do |scenario|
41
- sc=ActiveRecord::Model::Scenario.new
36
+ sc=Rutema::ActiveRecord::Scenario.new
42
37
  sc.name=scenario.sequence_name
43
38
  sc.number=scenario.sequence_id
44
39
  sc.start_time=scenario.start_time
@@ -61,7 +56,7 @@ module Rutema
61
56
  sc.attended=false
62
57
  end
63
58
  scenario.step_states.each do |number,step|
64
- st=ActiveRecord::Model::Step.new
59
+ st=Rutema::ActiveRecord::Step.new
65
60
  st.name=step[:name]
66
61
  st.number=number
67
62
  st.status="#{step[:status]}"
@@ -75,17 +70,13 @@ module Rutema
75
70
  run_entry.save!
76
71
  "activerecord reporter done"
77
72
  end
78
-
79
- def to_s
73
+ def to_s#:nodoc:
80
74
  "ActiveRecordReporter"
81
75
  end
82
-
83
76
  private
84
77
  def sanitize text
85
78
  return text.gsub("\000","") if text
86
79
  return ""
87
80
  end
88
-
89
81
  end
90
-
91
82
  end
@@ -1,9 +1,9 @@
1
1
  # Copyright (c) 2007-2010 Vassilis Rizopoulos. All rights reserved.
2
2
  $:.unshift File.join(File.dirname(__FILE__),"..")
3
- require 'rutema/specification'
4
-
5
3
  module Rutema
6
- #Reporter is meant as a base class for reporter classes.
4
+ #Reporter is meant as a base class for reporter classes. Which means that it is here for ducumentation purposes.
5
+ #
6
+ #In order to create act as a Reporter for Rutema a class only need to implement the #report method
7
7
  class Reporter
8
8
  #params should be a Hash containing the parameters used to initialize the class
9
9
  def initialize params
@@ -15,10 +15,6 @@ module Rutema
15
15
  #
16
16
  #parse_errors is an Array of {:filename,:error} hashes containing the errors encountered by the parser when loading the specifications
17
17
  def report specifications,runner_states,parse_errors,configuration
18
-
19
18
  end
20
19
  end
21
-
22
- private
23
-
24
20
  end
@@ -1,10 +1,8 @@
1
1
  # Copyright (c) 2007-2010 Vassilis Rizopoulos. All rights reserved.
2
2
  $:.unshift File.join(File.dirname(__FILE__),"..","..")
3
3
  require 'net/smtp'
4
- require 'rutema/reporter'
5
- require 'rutema/specification'
6
- require 'rutema/reporters/text'
7
4
  require 'mailfactory'
5
+ require 'rutema/reporters/text'
8
6
 
9
7
  module Rutema
10
8
  #The following configuration keys are used by EmailReporter:
@@ -53,8 +51,7 @@ module Rutema
53
51
  @verbose||=false
54
52
  @logger.info("Reporter '#{self.to_s}' registered")
55
53
  end
56
-
57
- def to_s
54
+ def to_s#:nodoc:
58
55
  list=@recipients.join(', ')
59
56
  "EmailReporter - #{@server}:#{@port} from #{@mail.from} to #{list}"
60
57
  end
@@ -1,3 +1,5 @@
1
+ # Copyright (c) 2007-2010 Vassilis Rizopoulos. All rights reserved.
2
+
1
3
  module Rutema
2
4
  #This reporter creates a simple text summary of a test run
3
5
  #
@@ -9,7 +11,6 @@ module Rutema
9
11
  @verbose=params[:verbose] if params
10
12
  @verbose||=false
11
13
  end
12
-
13
14
  #Returns the text summary
14
15
  #
15
16
  #runner_states is an Array of Patir::CommandSequenceStatus containing the stati of the last run (so it contains all the Scenario stati for the loaded tests)
@@ -59,7 +60,6 @@ module Rutema
59
60
  end
60
61
  msg<<"\nSuccesses:" unless successes.empty?
61
62
  msg<<scenario_summaries(successes,specifications)
62
-
63
63
  return msg
64
64
  end
65
65
 
@@ -1,7 +1,7 @@
1
+ # Copyright (c) 2007-2010 Vassilis Rizopoulos. All rights reserved.
1
2
  require 'yaml'
2
3
 
3
4
  module Rutema
4
-
5
5
  module YAML
6
6
  #Experimental reporter used to dump the data of a run on disk
7
7
  #
@@ -18,7 +18,6 @@ module Rutema
18
18
  @filename||="rutema.yaml"
19
19
  @logger.info("Reporter #{self.to_s} registered")
20
20
  end
21
-
22
21
  #We get all the data from a test run in here.
23
22
  def report specifications,runner_states,parse_errors,configuration
24
23
  run_entry={}