rutema 1.1.3 → 1.2.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.
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={}