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
@@ -0,0 +1,157 @@
|
|
1
|
+
# Copyright (c) 2007-2011 Vassilis Rizopoulos. All rights reserved.
|
2
|
+
$:.unshift File.join(File.dirname(__FILE__),'..','..')
|
3
|
+
|
4
|
+
module Rutema
|
5
|
+
#Runner executes TestScenario instances and maintains the state of all scenarios run.
|
6
|
+
class Runner
|
7
|
+
attr_reader :states,:number_of_runs,:context
|
8
|
+
attr_accessor :setup,:teardown
|
9
|
+
attr_writer :attended
|
10
|
+
|
11
|
+
#setup and teardown are TestScenario instances that will run before and after each call
|
12
|
+
#to the scenario.
|
13
|
+
def initialize context=nil,setup=nil, teardown=nil,logger=nil
|
14
|
+
@setup=setup
|
15
|
+
@teardown=teardown
|
16
|
+
@attended=false
|
17
|
+
@logger=logger
|
18
|
+
@logger||=Patir.setup_logger
|
19
|
+
@states=Hash.new
|
20
|
+
@number_of_runs=0
|
21
|
+
@context=context || Hash.new
|
22
|
+
end
|
23
|
+
|
24
|
+
#Tells you if the system runs in the mode that expects user input
|
25
|
+
def attended?
|
26
|
+
return @attended
|
27
|
+
end
|
28
|
+
#Runs a scenario and stores the result internally
|
29
|
+
#
|
30
|
+
#Returns the result of the run as a Patir::CommandSequenceStatus
|
31
|
+
def run name,scenario, run_setup=true
|
32
|
+
@logger.debug("Starting run for #{name} with #{scenario.inspect}")
|
33
|
+
@context[:scenario_name]=name
|
34
|
+
#if setup /teardown is defined we need to execute them before and after
|
35
|
+
if @setup && run_setup
|
36
|
+
@logger.info("Setup for #{name}")
|
37
|
+
@states["#{name}_setup"]=run_scenario("#{name}_setup",@setup)
|
38
|
+
@states["#{name}_setup"].sequence_id="s#{@number_of_runs}"
|
39
|
+
if @states["#{name}_setup"].executed?
|
40
|
+
#do not execute the scenario unless the setup was succesful
|
41
|
+
if @states["#{name}_setup"].success?
|
42
|
+
@logger.info("Scenario for #{name}")
|
43
|
+
@states[name]=run_scenario(name,scenario)
|
44
|
+
@states[name].sequence_id="#{@number_of_runs}"
|
45
|
+
else
|
46
|
+
@states[name]=initialize_state(name,scenario)
|
47
|
+
@states[name].sequence_id="#{@number_of_runs}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
else
|
51
|
+
@logger.info("Scenario for #{name}")
|
52
|
+
@states[name]=run_scenario(name,scenario)
|
53
|
+
@states[name].sequence_id="#{@number_of_runs}"
|
54
|
+
end
|
55
|
+
#no setup means no teardown
|
56
|
+
if @teardown && run_setup
|
57
|
+
#always execute teardown
|
58
|
+
@logger.warn("Teardown for #{name}")
|
59
|
+
@states["#{name}_teardown"]=run_scenario("#{name}_teardown",@teardown)
|
60
|
+
@states["#{name}_teardown"].sequence_id="#{@number_of_runs}t"
|
61
|
+
end
|
62
|
+
@number_of_runs+=1
|
63
|
+
@context[:scenario_name]=nil
|
64
|
+
return @states[name]
|
65
|
+
end
|
66
|
+
|
67
|
+
#Returns the state of the scenario with the given name.
|
68
|
+
#
|
69
|
+
#Will return nil if no scenario is found under that name.
|
70
|
+
def [](name)
|
71
|
+
return @states[name]
|
72
|
+
end
|
73
|
+
|
74
|
+
#Resets the Runner's internal state
|
75
|
+
def reset
|
76
|
+
@states.clear
|
77
|
+
@number_of_runs=0
|
78
|
+
end
|
79
|
+
|
80
|
+
#returns true if all the scenarios in the last run were succesful or if nothing was run yet
|
81
|
+
def success?
|
82
|
+
@success=true
|
83
|
+
@states.each do |k,v|
|
84
|
+
@success&=(v.status!=:error)
|
85
|
+
end
|
86
|
+
return @success
|
87
|
+
end
|
88
|
+
private
|
89
|
+
def run_scenario name,scenario
|
90
|
+
state=initialize_state(name,scenario)
|
91
|
+
begin
|
92
|
+
if evaluate_attention(scenario,state)
|
93
|
+
stps=scenario.steps
|
94
|
+
if stps.empty?
|
95
|
+
@logger.warn("Scenario #{name} contains no steps")
|
96
|
+
state.status=:warning
|
97
|
+
else
|
98
|
+
stps.each do |s|
|
99
|
+
state.step=run_step(s)
|
100
|
+
break if :error==state.status
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
rescue
|
105
|
+
@logger.error("Encountered error in #{name}: #{$!.message}")
|
106
|
+
@logger.debug($!)
|
107
|
+
state.status=:error
|
108
|
+
end
|
109
|
+
state.stop_time=Time.now
|
110
|
+
state.sequence_id=@number_of_runs
|
111
|
+
return state
|
112
|
+
end
|
113
|
+
def initialize_state name,scenario
|
114
|
+
state=Patir::CommandSequenceStatus.new(name,scenario.steps)
|
115
|
+
end
|
116
|
+
def evaluate_attention scenario,state
|
117
|
+
if scenario.attended?
|
118
|
+
if !self.attended?
|
119
|
+
@logger.warn("Attended scenario cannot be run in unattended mode")
|
120
|
+
state.status=:warning
|
121
|
+
return false
|
122
|
+
end
|
123
|
+
state.strategy=:attended
|
124
|
+
else
|
125
|
+
state.strategy=:unattended
|
126
|
+
end
|
127
|
+
return true
|
128
|
+
end
|
129
|
+
def run_step step
|
130
|
+
@logger.info("Running step #{step.number} - #{step.name}")
|
131
|
+
if step.has_cmd? && step.cmd.respond_to?(:run)
|
132
|
+
step.cmd.run(@context)
|
133
|
+
msg=step.to_s
|
134
|
+
if !step.cmd.success?
|
135
|
+
msg<<"\n#{step.cmd.output}" unless step.cmd.output.empty?
|
136
|
+
msg<<"\n#{step.cmd.error}" unless step.cmd.error.empty?
|
137
|
+
end
|
138
|
+
else
|
139
|
+
@logger.warn("No command associated with step '#{step.step_type}'. Step number is #{step.number}")
|
140
|
+
end
|
141
|
+
step.status=:success if step.status==:error && step.ignore?
|
142
|
+
log_step_result(step,msg)
|
143
|
+
return step
|
144
|
+
end
|
145
|
+
def log_step_result step,msg
|
146
|
+
if step.status==:error
|
147
|
+
if step.ignore?
|
148
|
+
@logger.warn("Step failed but result is being ignored!\n#{msg}")
|
149
|
+
else
|
150
|
+
@logger.error(msg)
|
151
|
+
end
|
152
|
+
else
|
153
|
+
@logger.info(msg) if msg && !msg.empty?
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Copyright (c) 2007-2011 Vassilis Rizopoulos. All rights reserved.
|
2
|
+
$:.unshift File.join(File.dirname(__FILE__),'..','..')
|
3
|
+
|
4
|
+
require 'rutema/runners/default'
|
5
|
+
|
6
|
+
module Rutema
|
7
|
+
#StepRunner halts before every step and asks if it should be executed or not.
|
8
|
+
class StepRunner<Runner
|
9
|
+
def initialize setup=nil, teardown=nil,logger=nil
|
10
|
+
@questioner=HighLine.new
|
11
|
+
super(setup,teardown,logger)
|
12
|
+
end
|
13
|
+
def run_step step
|
14
|
+
if @questioner.agree("Execute #{step.to_s}?")
|
15
|
+
return super(step)
|
16
|
+
else
|
17
|
+
msg="#{step.number} - #{step.step_type} - #{step.status}"
|
18
|
+
@logger.info(msg)
|
19
|
+
return step
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/rutema/system.rb
CHANGED
@@ -4,259 +4,27 @@ require 'rexml/document'
|
|
4
4
|
require 'patir/configuration'
|
5
5
|
require 'patir/command'
|
6
6
|
require 'patir/base'
|
7
|
-
require 'rutema/specification'
|
8
7
|
require 'rutema/configuration'
|
9
|
-
|
8
|
+
|
9
|
+
require 'rutema/parsers/base'
|
10
|
+
require 'rutema/parsers/xml'
|
11
|
+
|
12
|
+
require 'rutema/runners/default'
|
13
|
+
require 'rutema/runners/step'
|
14
|
+
|
15
|
+
require 'rutema/reporters/activerecord'
|
16
|
+
require 'rutema/reporters/text'
|
17
|
+
require 'rutema/reporters/yaml'
|
18
|
+
require 'rutema/reporters/email'
|
10
19
|
|
11
20
|
module Rutema
|
12
21
|
#This module defines the version numbers for the library
|
13
22
|
module Version
|
14
23
|
MAJOR=1
|
15
|
-
MINOR=
|
16
|
-
TINY=
|
24
|
+
MINOR=2
|
25
|
+
TINY=0
|
17
26
|
STRING=[ MAJOR, MINOR, TINY ].join( "." )
|
18
27
|
end
|
19
|
-
#The Elements module provides the namespace for the various modules adding parser functionality
|
20
|
-
module Elements
|
21
|
-
#Minimal offers a minimal(chic) set of elements for use in specifications
|
22
|
-
#
|
23
|
-
#These are:
|
24
|
-
# echo
|
25
|
-
# command
|
26
|
-
# prompt
|
27
|
-
module Minimal
|
28
|
-
#echo prints a message on the screen:
|
29
|
-
# <echo text="A meaningful message"/>
|
30
|
-
# <echo>A meaningful message</echo>
|
31
|
-
def element_echo step
|
32
|
-
step.cmd=Patir::RubyCommand.new("echo"){|cmd| cmd.error="";cmd.output="#{step.text}";$stdout.puts(cmd.output) ;:success}
|
33
|
-
end
|
34
|
-
#prompt asks the user a yes/no question. Answering yes means the step is succesful.
|
35
|
-
# <prompt text="Do you want fries with that?"/>
|
36
|
-
#
|
37
|
-
#A prompt element automatically makes a specification "attended"
|
38
|
-
def element_prompt step
|
39
|
-
step.attended=true
|
40
|
-
step.cmd=Patir::RubyCommand.new("prompt") do |cmd|
|
41
|
-
cmd.output=""
|
42
|
-
cmd.error=""
|
43
|
-
if HighLine.new.agree("#{step.text}")
|
44
|
-
step.output="y"
|
45
|
-
else
|
46
|
-
raise "n"
|
47
|
-
end#if
|
48
|
-
end#do rubycommand
|
49
|
-
end
|
50
|
-
#command executes a shell command
|
51
|
-
# <command cmd="useful_command.exe with parameters", working_directory="some/directory"/>
|
52
|
-
def element_command step
|
53
|
-
raise ParserError,"missing required attribute cmd in #{step}" unless step.has_cmd?
|
54
|
-
wd=Dir.pwd
|
55
|
-
wd=step.working_directory if step.has_working_directory?
|
56
|
-
step.cmd=Patir::ShellCommand.new(:cmd=>step.cmd,:working_directory=>File.expand_path(wd))
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
#Is raised when an error is found in a specification
|
61
|
-
class ParserError<RuntimeError
|
62
|
-
end
|
63
|
-
|
64
|
-
#Base class that bombs out when used.
|
65
|
-
#
|
66
|
-
#Initialze expects a hash and as a base implementation assigns :logger as the internal logger.
|
67
|
-
#
|
68
|
-
#By default the internal logger will log to the console if no logger is provided.
|
69
|
-
class SpecificationParser
|
70
|
-
attr_reader :configuration
|
71
|
-
def initialize params
|
72
|
-
@logger=params[:logger]
|
73
|
-
@logger||=Patir.setup_logger
|
74
|
-
@configuration=params[:configuration]
|
75
|
-
@logger.warn("No system configuration provided to the parser") unless @configuration
|
76
|
-
end
|
77
|
-
|
78
|
-
def parse_specification param
|
79
|
-
raise ParserError,"not implemented. You should derive a parser implementation from SpecificationParser!"
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
#BaseXMLParser encapsulates all the XML parsing code
|
84
|
-
class BaseXMLParser<SpecificationParser
|
85
|
-
ELEM_SPEC="specification"
|
86
|
-
ELEM_DESC="specification/description"
|
87
|
-
ELEM_TITLE="specification/title"
|
88
|
-
ELEM_SCENARIO="specification/scenario"
|
89
|
-
ELEM_REQ="requirement"
|
90
|
-
#Parses __param__ and returns the Rutema::TestSpecification instance
|
91
|
-
#
|
92
|
-
#param can be the filename of the specification or the contents of that file.
|
93
|
-
#
|
94
|
-
#Will throw ParserError if something goes wrong
|
95
|
-
def parse_specification param
|
96
|
-
@logger.debug("Loading #{param}")
|
97
|
-
begin
|
98
|
-
if File.exists?(param)
|
99
|
-
#read the file
|
100
|
-
txt=File.read(param)
|
101
|
-
filename=File.expand_path(param)
|
102
|
-
else
|
103
|
-
filename=Dir.pwd
|
104
|
-
#try to parse the parameter
|
105
|
-
txt=param
|
106
|
-
end
|
107
|
-
spec=parse_case(txt,filename)
|
108
|
-
raise "Missing required attribute 'name' in specification element" unless spec.has_name? && !spec.name.empty?
|
109
|
-
return spec
|
110
|
-
rescue
|
111
|
-
@logger.debug($!)
|
112
|
-
raise ParserError,"Error loading #{param}: #{$!.message}"
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
private
|
117
|
-
#Parses the XML specification of a testcase and creates the corresponding TestSpecification instance
|
118
|
-
def parse_case xmltxt,filename
|
119
|
-
#the testspec to return
|
120
|
-
spec=TestSpecification.new
|
121
|
-
#read the test spec
|
122
|
-
xmldoc=REXML::Document.new( xmltxt )
|
123
|
-
#validate it
|
124
|
-
validate_case(xmldoc)
|
125
|
-
#parse it
|
126
|
-
el=xmldoc.elements[ELEM_SPEC]
|
127
|
-
xmldoc.root.attributes.each do |attr,value|
|
128
|
-
add_attribute(spec,attr,value)
|
129
|
-
end
|
130
|
-
#get the title
|
131
|
-
spec.title=xmldoc.elements[ELEM_TITLE].text
|
132
|
-
spec.title||=""
|
133
|
-
spec.title.strip!
|
134
|
-
#get the description
|
135
|
-
#strip line feeds, cariage returns and remove all tabs
|
136
|
-
spec.description=xmldoc.elements[ELEM_DESC].text
|
137
|
-
spec.description||=""
|
138
|
-
begin
|
139
|
-
spec.description.strip!
|
140
|
-
spec.description.gsub!(/\t/,'')
|
141
|
-
end unless spec.description.empty?
|
142
|
-
#get the requirements
|
143
|
-
reqs=el.elements.select{|e| e.name==ELEM_REQ}
|
144
|
-
reqs.collect!{|r| r.attributes["name"]}
|
145
|
-
spec.requirements=reqs
|
146
|
-
#Get the scenario
|
147
|
-
Dir.chdir(File.dirname(filename)) do
|
148
|
-
spec.scenario=parse_scenario(xmldoc.elements[ELEM_SCENARIO].to_s) if xmldoc.elements[ELEM_SCENARIO]
|
149
|
-
end
|
150
|
-
spec.filename=filename
|
151
|
-
return spec
|
152
|
-
end
|
153
|
-
#Validates the XML file from our point of view.
|
154
|
-
#
|
155
|
-
#Checks for the existence of ELEM_SPEC, ELEM_DESC and ELEM_TITLE and raises ParserError if they're missing.
|
156
|
-
def validate_case xmldoc
|
157
|
-
raise ParserError,"missing #{ELEM_SPEC} element" unless xmldoc.elements[ELEM_SPEC]
|
158
|
-
raise ParserError,"missing #{ELEM_DESC} element" unless xmldoc.elements[ELEM_DESC]
|
159
|
-
raise ParserError,"missing #{ELEM_TITLE} element" unless xmldoc.elements[ELEM_TITLE]
|
160
|
-
end
|
161
|
-
|
162
|
-
#Parses the scenario XML element and returns the Rutema::TestScenario instance
|
163
|
-
def parse_scenario xmltxt
|
164
|
-
@logger.debug("Parsing scenario from #{xmltxt}")
|
165
|
-
scenario=Rutema::TestScenario.new
|
166
|
-
xmldoc=REXML::Document.new( xmltxt )
|
167
|
-
xmldoc.root.attributes.each do |attr,value|
|
168
|
-
add_attribute(scenario,attr,value)
|
169
|
-
end
|
170
|
-
number=0
|
171
|
-
xmldoc.root.elements.each do |el|
|
172
|
-
step=parse_step(el.to_s)
|
173
|
-
if step.step_type=="include_scenario"
|
174
|
-
included_scenario=include_scenario(step)
|
175
|
-
included_scenario.steps.each do |st|
|
176
|
-
@logger.debug("Adding included step #{st}")
|
177
|
-
number+=1
|
178
|
-
st.number=number
|
179
|
-
st.included_in=step.file
|
180
|
-
scenario.add_step(st)
|
181
|
-
end
|
182
|
-
else
|
183
|
-
number+=1
|
184
|
-
step.number=number
|
185
|
-
scenario.add_step(step)
|
186
|
-
end
|
187
|
-
end
|
188
|
-
return scenario
|
189
|
-
end
|
190
|
-
|
191
|
-
#Parses xml and returns the Rutema::TestStep instance
|
192
|
-
def parse_step xmltxt
|
193
|
-
xmldoc=REXML::Document.new( xmltxt )
|
194
|
-
#any step element
|
195
|
-
step=Rutema::TestStep.new()
|
196
|
-
step.ignore=false
|
197
|
-
xmldoc.root.attributes.each do |attr,value|
|
198
|
-
add_attribute(step,attr,value)
|
199
|
-
end
|
200
|
-
step.text=xmldoc.root.text.strip if xmldoc.root.text
|
201
|
-
step.step_type=xmldoc.root.name
|
202
|
-
return step
|
203
|
-
end
|
204
|
-
|
205
|
-
def add_attribute element,attr,value
|
206
|
-
if boolean?(value)
|
207
|
-
element.attribute(attr,eval(value))
|
208
|
-
else
|
209
|
-
element.attribute(attr,value)
|
210
|
-
end
|
211
|
-
end
|
212
|
-
|
213
|
-
def boolean? attribute_value
|
214
|
-
return true if attribute_value=="true" || attribute_value=="false"
|
215
|
-
return false
|
216
|
-
end
|
217
|
-
|
218
|
-
#handles <include_scenario> elements, adding the steps to the current scenario
|
219
|
-
def include_scenario step
|
220
|
-
@logger.debug("Including file from #{step}")
|
221
|
-
raise ParserError,"missing required attribute file in #{step}" unless step.has_file?
|
222
|
-
raise ParserError,"Cannot find #{File.expand_path(step.file)}" unless File.exists?(File.expand_path(step.file))
|
223
|
-
#Load the scenario
|
224
|
-
step.file=File.expand_path(step.file)
|
225
|
-
include_content=File.read(step.file)
|
226
|
-
@logger.debug(include_content)
|
227
|
-
return parse_scenario(include_content)
|
228
|
-
end
|
229
|
-
end
|
230
|
-
#The ExtensibleXMLParser allows you to easily add methods to handle specification elements.
|
231
|
-
#
|
232
|
-
#A method element_foo(step) allows you to add behaviour for foo scenario elements.
|
233
|
-
#
|
234
|
-
#The method will receive a Rutema::TestStep instance.
|
235
|
-
class ExtensibleXMLParser<BaseXMLParser
|
236
|
-
def parse_specification param
|
237
|
-
spec = super(param)
|
238
|
-
#change into the directory the spec is in to handle relative paths correctly
|
239
|
-
Dir.chdir(File.dirname(File.expand_path(spec.filename))) do |path|
|
240
|
-
#iterate through the steps
|
241
|
-
spec.scenario.steps.each do |step|
|
242
|
-
#do we have a method to handle the element?
|
243
|
-
if respond_to?(:"element_#{step.step_type}")
|
244
|
-
begin
|
245
|
-
self.send(:"element_#{step.step_type}",step)
|
246
|
-
rescue
|
247
|
-
raise ParserError, $!.message
|
248
|
-
end
|
249
|
-
end
|
250
|
-
end
|
251
|
-
end
|
252
|
-
return spec
|
253
|
-
end
|
254
|
-
end
|
255
|
-
#MinimalXMLParser offers three runnable steps in the scenarios as defined in Rutema::Elements::Minimal
|
256
|
-
class MinimalXMLParser<ExtensibleXMLParser
|
257
|
-
include Rutema::Elements::Minimal
|
258
|
-
end
|
259
|
-
|
260
28
|
#This class coordinates parsing, execution and reporting of test specifications
|
261
29
|
class Coordinator
|
262
30
|
attr_accessor :configuration,:parse_errors,:parsed_files
|
@@ -358,7 +126,7 @@ module Rutema
|
|
358
126
|
def last_run_a_success?
|
359
127
|
return @runner.success?
|
360
128
|
end
|
361
|
-
def to_s
|
129
|
+
def to_s#:nodoc:
|
362
130
|
"Parsed #{@parsed_files.size} files\n#{TextReporter.new.report(@specifications,@runner.states.values,@parse_errors,@configuration)}"
|
363
131
|
end
|
364
132
|
private
|
@@ -457,178 +225,6 @@ module Rutema
|
|
457
225
|
return status
|
458
226
|
end
|
459
227
|
end
|
460
|
-
|
461
|
-
#Runner executes TestScenario instances and maintains the state of all scenarios run.
|
462
|
-
class Runner
|
463
|
-
attr_reader :states,:number_of_runs,:context
|
464
|
-
attr_accessor :setup,:teardown
|
465
|
-
attr_writer :attended
|
466
|
-
|
467
|
-
#setup and teardown are TestScenario instances that will run before and after each call
|
468
|
-
#to the scenario.
|
469
|
-
def initialize context=nil,setup=nil, teardown=nil,logger=nil
|
470
|
-
@setup=setup
|
471
|
-
@teardown=teardown
|
472
|
-
@attended=false
|
473
|
-
@logger=logger
|
474
|
-
@logger||=Patir.setup_logger
|
475
|
-
@states=Hash.new
|
476
|
-
@number_of_runs=0
|
477
|
-
@context=context || Hash.new
|
478
|
-
end
|
479
|
-
|
480
|
-
#Tells you if the system runs in the mode that expects user input
|
481
|
-
def attended?
|
482
|
-
return @attended
|
483
|
-
end
|
484
|
-
#Runs a scenario and stores the result internally
|
485
|
-
#
|
486
|
-
#Returns the result of the run as a Patir::CommandSequenceStatus
|
487
|
-
def run name,scenario, run_setup=true
|
488
|
-
@logger.debug("Starting run for #{name} with #{scenario.inspect}")
|
489
|
-
@context[:scenario_name]=name
|
490
|
-
#if setup /teardown is defined we need to execute them before and after
|
491
|
-
if @setup && run_setup
|
492
|
-
@logger.info("Setup for #{name}")
|
493
|
-
@states["#{name}_setup"]=run_scenario("#{name}_setup",@setup)
|
494
|
-
@states["#{name}_setup"].sequence_id="s#{@number_of_runs}"
|
495
|
-
if @states["#{name}_setup"].executed?
|
496
|
-
#do not execute the scenario unless the setup was succesful
|
497
|
-
if @states["#{name}_setup"].success?
|
498
|
-
@logger.info("Scenario for #{name}")
|
499
|
-
@states[name]=run_scenario(name,scenario)
|
500
|
-
@states[name].sequence_id="#{@number_of_runs}"
|
501
|
-
else
|
502
|
-
@states[name]=initialize_state(name,scenario)
|
503
|
-
@states[name].sequence_id="#{@number_of_runs}"
|
504
|
-
end
|
505
|
-
end
|
506
|
-
else
|
507
|
-
@logger.info("Scenario for #{name}")
|
508
|
-
@states[name]=run_scenario(name,scenario)
|
509
|
-
@states[name].sequence_id="#{@number_of_runs}"
|
510
|
-
end
|
511
|
-
#no setup means no teardown
|
512
|
-
if @teardown && run_setup
|
513
|
-
#always execute teardown
|
514
|
-
@logger.warn("Teardown for #{name}")
|
515
|
-
@states["#{name}_teardown"]=run_scenario("#{name}_teardown",@teardown)
|
516
|
-
@states["#{name}_teardown"].sequence_id="#{@number_of_runs}t"
|
517
|
-
end
|
518
|
-
@number_of_runs+=1
|
519
|
-
@context[:scenario_name]=nil
|
520
|
-
return @states[name]
|
521
|
-
end
|
522
|
-
|
523
|
-
#Returns the state of the scenario with the given name.
|
524
|
-
#
|
525
|
-
#Will return nil if no scenario is found under that name.
|
526
|
-
def [](name)
|
527
|
-
return @states[name]
|
528
|
-
end
|
529
|
-
|
530
|
-
#Resets the Runner's internal state
|
531
|
-
def reset
|
532
|
-
@states.clear
|
533
|
-
@number_of_runs=0
|
534
|
-
end
|
535
|
-
|
536
|
-
#returns true if all the scenarios in the last run were succesful or if nothing was run yet
|
537
|
-
def success?
|
538
|
-
@success=true
|
539
|
-
@states.each do |k,v|
|
540
|
-
@success&=(v.status!=:error)
|
541
|
-
end
|
542
|
-
return @success
|
543
|
-
end
|
544
|
-
private
|
545
|
-
def run_scenario name,scenario
|
546
|
-
state=initialize_state(name,scenario)
|
547
|
-
begin
|
548
|
-
if evaluate_attention(scenario,state)
|
549
|
-
stps=scenario.steps
|
550
|
-
if stps.empty?
|
551
|
-
@logger.warn("Scenario #{name} contains no steps")
|
552
|
-
state.status=:warning
|
553
|
-
else
|
554
|
-
stps.each do |s|
|
555
|
-
state.step=run_step(s)
|
556
|
-
break if :error==state.status
|
557
|
-
end
|
558
|
-
end
|
559
|
-
end
|
560
|
-
rescue
|
561
|
-
@logger.error("Encountered error in #{name}: #{$!.message}")
|
562
|
-
@logger.debug($!)
|
563
|
-
state.status=:error
|
564
|
-
end
|
565
|
-
state.stop_time=Time.now
|
566
|
-
state.sequence_id=@number_of_runs
|
567
|
-
return state
|
568
|
-
end
|
569
|
-
def initialize_state name,scenario
|
570
|
-
state=Patir::CommandSequenceStatus.new(name,scenario.steps)
|
571
|
-
end
|
572
|
-
def evaluate_attention scenario,state
|
573
|
-
if scenario.attended?
|
574
|
-
if !self.attended?
|
575
|
-
@logger.warn("Attended scenario cannot be run in unattended mode")
|
576
|
-
state.status=:warning
|
577
|
-
return false
|
578
|
-
end
|
579
|
-
state.strategy=:attended
|
580
|
-
else
|
581
|
-
state.strategy=:unattended
|
582
|
-
end
|
583
|
-
return true
|
584
|
-
end
|
585
|
-
def run_step step
|
586
|
-
@logger.info("Running step #{step.number} - #{step.name}")
|
587
|
-
if step.has_cmd? && step.cmd.respond_to?(:run)
|
588
|
-
step.cmd.run(@context)
|
589
|
-
msg=step.to_s
|
590
|
-
if !step.cmd.success?
|
591
|
-
msg<<"\n#{step.cmd.output}" unless step.cmd.output.empty?
|
592
|
-
msg<<"\n#{step.cmd.error}" unless step.cmd.error.empty?
|
593
|
-
end
|
594
|
-
else
|
595
|
-
@logger.warn("No command associated with step '#{step.step_type}'. Step number is #{step.number}")
|
596
|
-
end
|
597
|
-
step.status=:success if step.status==:error && step.ignore?
|
598
|
-
log_step_result(step,msg)
|
599
|
-
return step
|
600
|
-
end
|
601
|
-
def log_step_result step,msg
|
602
|
-
if step.status==:error
|
603
|
-
if step.ignore?
|
604
|
-
@logger.warn("Step failed but result is being ignored!\n#{msg}")
|
605
|
-
else
|
606
|
-
@logger.error(msg)
|
607
|
-
end
|
608
|
-
else
|
609
|
-
@logger.info(msg) if msg && !msg.empty?
|
610
|
-
end
|
611
|
-
end
|
612
|
-
|
613
|
-
end
|
614
|
-
|
615
|
-
#StepRunner halts before every step and asks if it should be executed or not.
|
616
|
-
class StepRunner<Runner
|
617
|
-
def initialize context=nil,setup=nil, teardown=nil,logger=nil
|
618
|
-
@questioner=HighLine.new
|
619
|
-
super(context,setup,teardown,logger)
|
620
|
-
end
|
621
|
-
def run_step step
|
622
|
-
if @questioner.agree("Execute #{step.to_s}?")
|
623
|
-
return super(step)
|
624
|
-
else
|
625
|
-
msg="#{step.number} - #{step.step_type} - #{step.status}"
|
626
|
-
@logger.info(msg)
|
627
|
-
return step
|
628
|
-
end
|
629
|
-
end
|
630
|
-
end
|
631
|
-
|
632
228
|
#The "executioner" application class
|
633
229
|
#
|
634
230
|
#Parses the commandline, sets up the configuration and launches Cordinator
|
@@ -640,7 +236,7 @@ module Rutema
|
|
640
236
|
@logger.info("rutemax v#{Version::STRING}")
|
641
237
|
begin
|
642
238
|
raise "No configuration file defined!" if !@config_file
|
643
|
-
@configuration=
|
239
|
+
@configuration=RutemaConfigurator.new(@config_file,@logger).configuration
|
644
240
|
@configuration.context[:config_file]=File.basename(@config_file)
|
645
241
|
@configuration.use_step_by_step=@step
|
646
242
|
Dir.chdir(File.dirname(@config_file)) do
|
@@ -721,5 +317,4 @@ module Rutema
|
|
721
317
|
end
|
722
318
|
end
|
723
319
|
end
|
724
|
-
|
725
320
|
end
|