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.
- 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
|