rutema 1.1.3 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemtest +0 -0
- data/History.txt +5 -0
- data/Manifest.txt +28 -23
- data/README.md +91 -0
- data/README.txt +2 -10
- data/Rakefile +6 -9
- data/bin/rutema +9 -0
- data/bin/rutemax +2 -0
- data/lib/rutema/configuration.rb +7 -8
- data/lib/rutema/elements/minimal.rb +45 -0
- data/lib/rutema/gems.rb +3 -3
- data/lib/rutema/models/activerecord.rb +164 -0
- data/lib/rutema/models/base.rb +5 -0
- data/lib/rutema/{specification.rb → objectmodel.rb} +4 -10
- data/lib/rutema/parsers/base.rb +29 -0
- data/lib/rutema/parsers/xml.rb +186 -0
- data/lib/rutema/reporters/activerecord.rb +9 -18
- data/lib/rutema/{reporter.rb → reporters/base.rb} +3 -7
- data/lib/rutema/reporters/email.rb +2 -5
- data/lib/rutema/reporters/text.rb +2 -2
- data/lib/rutema/reporters/yaml.rb +1 -2
- data/lib/rutema/runners/default.rb +157 -0
- data/lib/rutema/runners/step.rb +23 -0
- data/lib/rutema/system.rb +15 -420
- data/test/distro_test/config/full.rutema +2 -0
- data/test/distro_test/specs/check.spec +8 -0
- data/test/distro_test/specs/duplicate_name.spec +8 -0
- data/test/distro_test/specs/fail.spec +9 -0
- data/test/distro_test/specs/setup.spec +8 -0
- data/test/distro_test/specs/teardown.spec +8 -0
- data/test/rutema.rutema +1 -1
- data/test/rutema.spec +5 -5
- data/test/test_activerecord.rb +0 -0
- data/test/test_configuration.rb +7 -9
- data/test/test_couchdb.rb +14 -0
- data/test/{test_specification.rb → test_objectmodel.rb} +8 -11
- data/test/test_parsers.rb +136 -0
- data/test/test_rake.rb +2 -3
- data/test/{test_reporter.rb → test_reporters.rb} +0 -0
- data/test/test_runners.rb +72 -0
- data/test/test_system.rb +3 -166
- metadata +57 -62
- data/bin/rutema_upgrader +0 -81
- data/distro_test.sh +0 -6
- data/lib/rutema/model.rb +0 -231
- data/lib/rutema/reporters/couchdb.rb +0 -62
- data/lib/rutema/reporters/standard_reporters.rb +0 -7
- data/selftest.sh +0 -2
- data/test/data/sample09.db +0 -0
- data/test/migration.spec +0 -9
- data/test/test_model.rb +0 -62
@@ -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/
|
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::
|
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::
|
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::
|
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::
|
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::
|
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={}
|