rutema 2.0.0 → 2.0.1
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.
- checksums.yaml +5 -5
- data/History.txt +2 -0
- data/README.md +3 -3
- data/bin/rutema +4 -2
- data/lib/rutema/application.rb +22 -4
- data/lib/rutema/core/configuration.rb +277 -98
- data/lib/rutema/core/engine.rb +177 -33
- data/lib/rutema/core/framework.rb +115 -26
- data/lib/rutema/core/objectmodel.rb +284 -222
- data/lib/rutema/core/parser.rb +41 -10
- data/lib/rutema/core/reporter.rb +39 -16
- data/lib/rutema/core/runner.rb +103 -35
- data/lib/rutema/elements/minimal.rb +12 -9
- data/lib/rutema/parsers/xml.rb +63 -11
- data/lib/rutema/reporters/json.rb +4 -2
- data/lib/rutema/reporters/junit.rb +35 -12
- data/lib/rutema/version.rb +21 -6
- metadata +24 -20
|
@@ -1,222 +1,284 @@
|
|
|
1
|
-
# Copyright (c) 2007-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
#
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
@attributes
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
#
|
|
105
|
-
def
|
|
106
|
-
@
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
#
|
|
114
|
-
#
|
|
115
|
-
#
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
#
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
#
|
|
142
|
-
#
|
|
143
|
-
#
|
|
144
|
-
#
|
|
145
|
-
#
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
@attributes[:cmd]
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
def
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
end
|
|
1
|
+
# Copyright (c) 2007-2021 Vassilis Rizopoulos. All rights reserved.
|
|
2
|
+
|
|
3
|
+
require 'patir/command'
|
|
4
|
+
|
|
5
|
+
module Rutema
|
|
6
|
+
##
|
|
7
|
+
# Mix-in module allowing to add attributes to a class on the go
|
|
8
|
+
#
|
|
9
|
+
# This allows to add attributes to classes on the go and makes the accessor
|
|
10
|
+
# methods appear automatically too.
|
|
11
|
+
#
|
|
12
|
+
# It will add a #has_attribute? method too to query if an attribute is part of
|
|
13
|
+
# the object or not.
|
|
14
|
+
module SpecificationElement
|
|
15
|
+
##
|
|
16
|
+
# Add an attribute with a given value to the class instance
|
|
17
|
+
#
|
|
18
|
+
# * +symbol+ - a symbol or a string (which will be converted to a symbol
|
|
19
|
+
# internally) which shall be the name of the new attribute
|
|
20
|
+
# * +value+ - the initial value of the attribute
|
|
21
|
+
#
|
|
22
|
+
# If +symbol+ is neither a string nor a symbol this method will be a no-op.
|
|
23
|
+
def attribute symbol,value
|
|
24
|
+
@attributes||=Hash.new
|
|
25
|
+
case symbol
|
|
26
|
+
when String then @attributes[:"#{symbol}"]=value
|
|
27
|
+
when Symbol then @attributes[symbol]=value
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
##
|
|
32
|
+
# Method enabling to call object.attribute, object.attribute=, object.attribute? and object.has_attribute?
|
|
33
|
+
#
|
|
34
|
+
# object.attribute and object.attribute? will throw NoMethodError if the
|
|
35
|
+
# attribute is not set on the object instance.
|
|
36
|
+
#
|
|
37
|
+
# object.attribute= will set the attribute to the right operand and
|
|
38
|
+
# object.has_attribute? will return +true+ or +false+ according to the
|
|
39
|
+
# existence of the attribute.
|
|
40
|
+
def method_missing symbol,*args
|
|
41
|
+
@attributes||=Hash.new
|
|
42
|
+
key=symbol.id2name.chomp('?').chomp('=').sub(/^has_/,"")
|
|
43
|
+
@attributes[:"#{key}"]=args[0] if key+"="==symbol.id2name
|
|
44
|
+
if @attributes.has_key?(:"#{key}")
|
|
45
|
+
return true if "has_"+key+"?"==symbol.id2name
|
|
46
|
+
return @attributes[:"#{key}"]
|
|
47
|
+
else
|
|
48
|
+
return false if "has_"+key+"?"==symbol.id2name
|
|
49
|
+
super(symbol,*args)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
##
|
|
54
|
+
# Return +true+ if the object responds to the given method or else +false+
|
|
55
|
+
def respond_to? symbol,include_all
|
|
56
|
+
@attributes||=Hash.new
|
|
57
|
+
key=symbol.id2name.chomp('?').chomp('=').sub(/^has_/,"")
|
|
58
|
+
if @attributes.has_key?(:"#{key}")
|
|
59
|
+
return true
|
|
60
|
+
else
|
|
61
|
+
super(symbol,include_all)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
##
|
|
67
|
+
# A Rutema::Specification encompasses all elements required to run a test, the builds used, the scenario to run,
|
|
68
|
+
# together with a textual description and information that aids in tracing the test back to the requirements.
|
|
69
|
+
class Specification
|
|
70
|
+
include SpecificationElement
|
|
71
|
+
|
|
72
|
+
attr_accessor :scenario
|
|
73
|
+
##
|
|
74
|
+
# Expects a Hash of parameters
|
|
75
|
+
#
|
|
76
|
+
# Following keys have meaning in initialization:
|
|
77
|
+
#
|
|
78
|
+
# :name - the name of the testcase. Should uniquely identify the testcase
|
|
79
|
+
#
|
|
80
|
+
# :title - a one liner describing what the testcase does
|
|
81
|
+
#
|
|
82
|
+
# :filename - the filename describing the testcase
|
|
83
|
+
#
|
|
84
|
+
# :description - a full textual description for the testcase. To be used in reports and documents
|
|
85
|
+
#
|
|
86
|
+
# :scenario - An instance of Rutema::Scenario
|
|
87
|
+
#
|
|
88
|
+
# :version - The version of this specification
|
|
89
|
+
#
|
|
90
|
+
# Default values are empty strings and arrays. (scenario is nil)
|
|
91
|
+
def initialize params
|
|
92
|
+
begin
|
|
93
|
+
@attributes=params
|
|
94
|
+
end if params
|
|
95
|
+
@attributes||=Hash.new
|
|
96
|
+
@attributes[:name]||=""
|
|
97
|
+
@attributes[:title]||=""
|
|
98
|
+
@attributes[:filename]||=""
|
|
99
|
+
@attributes[:description]||=""
|
|
100
|
+
@scenario=@attributes[:scenario]
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
##
|
|
104
|
+
# Create a concise string representation consisting of +name+ and +title+
|
|
105
|
+
def to_s
|
|
106
|
+
return "#{@attributes[:name]} - #{@attributes[:title]}"
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
#A Rutema::Scenario is a sequence of Rutema::Step instances.
|
|
111
|
+
#
|
|
112
|
+
#Rutema::Step instances are run in the definition sequence and the scenario
|
|
113
|
+
#is succesfull when all steps are succesfull.
|
|
114
|
+
#
|
|
115
|
+
#From the execution point of view each step is either succesfull or failed and it depends on
|
|
116
|
+
#the exit code of the step's command.
|
|
117
|
+
#
|
|
118
|
+
#Failure in a step results in the interruption of execution and the report of the errors.
|
|
119
|
+
class Scenario
|
|
120
|
+
include SpecificationElement
|
|
121
|
+
|
|
122
|
+
attr_reader :steps
|
|
123
|
+
|
|
124
|
+
def initialize steps
|
|
125
|
+
@attributes=Hash.new
|
|
126
|
+
@steps=steps
|
|
127
|
+
@steps||=Array.new
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
#Adds a step at the end of the step sequence
|
|
131
|
+
def add_step step
|
|
132
|
+
@steps<<step
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
#Overwrites the step sequence
|
|
136
|
+
def steps= array_of_steps
|
|
137
|
+
@steps=array_of_steps
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
#Represents a step in a Scenario.
|
|
142
|
+
#
|
|
143
|
+
#Each Rutema::Step can have text and a command associated with it.
|
|
144
|
+
#
|
|
145
|
+
#Step standard attributes are.
|
|
146
|
+
#
|
|
147
|
+
#attended - the step can only run in attended mode, it requires user input.
|
|
148
|
+
#
|
|
149
|
+
#step_type - a string identifying the type of the step. It is "step" by default.
|
|
150
|
+
#
|
|
151
|
+
#ignore - set to true if the step's success or failure is to be ignored. It essentially means that the step is always considered succesfull
|
|
152
|
+
#
|
|
153
|
+
#continue - set to true if the step's success or failure is to be recognized, but following steps in the same scenario are to be carried out regardless
|
|
154
|
+
# of if indicates test failure, but ensures any further state is carried out as needed
|
|
155
|
+
#
|
|
156
|
+
# skip_on_error - if the test case has been marked a failure, skip steps marked with this attribute
|
|
157
|
+
#
|
|
158
|
+
#number - this is set when the step is assigned to a Scenario and is the sequence number
|
|
159
|
+
#
|
|
160
|
+
#cmd - the command associated with this step. This should quack like Patir::Command.
|
|
161
|
+
#
|
|
162
|
+
#status - one of :not_executed, :success, :warning, :error. Encapsulates the underlying command's status
|
|
163
|
+
#
|
|
164
|
+
#==Dynamic behaviour
|
|
165
|
+
#
|
|
166
|
+
#A Rutema::Step can be queried dynamicaly about the attributes it posesses:
|
|
167
|
+
# step.has_script? - will return true if script is step's attribute.
|
|
168
|
+
#Attribute's are mostly assigned by the parser, i.e. the Rutema::BaseXMLParser from the XML element
|
|
169
|
+
# <test script="some_script"/>
|
|
170
|
+
#will create a Step instance with step_type=="test" and script="some_script". In this case
|
|
171
|
+
#
|
|
172
|
+
# step.has_script? returns true
|
|
173
|
+
# step.script returns "some_script"
|
|
174
|
+
#
|
|
175
|
+
#Just like an OpenStruct, Step attributes will be created by direct assignment:
|
|
176
|
+
# step.script="some_script" creates the script attribute if it does not exist.
|
|
177
|
+
#
|
|
178
|
+
#See Rutema::SpecificationElement for the implementation details.
|
|
179
|
+
class Step
|
|
180
|
+
include SpecificationElement
|
|
181
|
+
include Patir::Command
|
|
182
|
+
|
|
183
|
+
#_txt_ describes the step, _cmd_ is the command to run
|
|
184
|
+
def initialize txt="",cmd=nil
|
|
185
|
+
@attributes=Hash.new
|
|
186
|
+
#ignore is off by default
|
|
187
|
+
@attributes[:ignore]=false
|
|
188
|
+
# continue is off by default
|
|
189
|
+
@attributes[:continue] = false
|
|
190
|
+
# skip_on_error is off by default
|
|
191
|
+
@attributes[:skip_on_error]=false
|
|
192
|
+
#assign
|
|
193
|
+
@attributes[:cmd]=cmd if cmd
|
|
194
|
+
@attributes[:text]=txt
|
|
195
|
+
@number=0
|
|
196
|
+
@attributes[:step_type]="step"
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def name
|
|
200
|
+
return name_with_parameters
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def output
|
|
204
|
+
return "" unless @attributes[:cmd]
|
|
205
|
+
return @attributes[:cmd].output
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def error
|
|
209
|
+
return "no command associated" unless @attributes[:cmd]
|
|
210
|
+
return @attributes[:cmd].error
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def backtrace
|
|
214
|
+
return "no backtrace associated" unless @attributes[:cmd]
|
|
215
|
+
return @attributes[:cmd].backtrace if @attributes[:cmd].respond_to?(:backtrace)
|
|
216
|
+
return ""
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def skip_on_error?
|
|
220
|
+
return false unless @attributes[:skip_on_error]
|
|
221
|
+
return @attributes[:skip_on_error]
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def ignore?
|
|
225
|
+
return false unless @attributes[:ignore]
|
|
226
|
+
return @attributes[:ignore]
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def continue?
|
|
230
|
+
return false unless @attributes[:continue]
|
|
231
|
+
return @attributes[:continue]
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def exec_time
|
|
235
|
+
return 0 unless @attributes[:cmd]
|
|
236
|
+
return @attributes[:cmd].exec_time
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def status
|
|
240
|
+
return :warning unless @attributes[:cmd]
|
|
241
|
+
return @attributes[:cmd].status
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def status= st
|
|
245
|
+
@attributes[:cmd].status=st if @attributes[:cmd]
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def run context=nil
|
|
249
|
+
return not_executed unless @attributes[:cmd]
|
|
250
|
+
return @attributes[:cmd].run(context)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def reset
|
|
254
|
+
@attributes[:cmd].reset if @attributes[:cmd]
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def name_with_parameters
|
|
258
|
+
param=" - #{self.cmd.to_s}" if self.has_cmd?
|
|
259
|
+
return "#{@attributes[:step_type]}#{param}"
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def to_s#:nodoc:
|
|
263
|
+
if self.has_cmd?
|
|
264
|
+
msg="#{self.number} - #{self.cmd.to_s}"
|
|
265
|
+
else
|
|
266
|
+
msg="#{self.number} - #{self.name}"
|
|
267
|
+
end
|
|
268
|
+
msg<<" in #{self.included_in}" if self.has_included_in?
|
|
269
|
+
return msg
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
class Patir::ShellCommand
|
|
275
|
+
def to_s#:nodoc:
|
|
276
|
+
return @command
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
class Patir::RubyCommand
|
|
281
|
+
def to_s#:nodoc:
|
|
282
|
+
return @name
|
|
283
|
+
end
|
|
284
|
+
end
|
data/lib/rutema/core/parser.rb
CHANGED
|
@@ -1,35 +1,66 @@
|
|
|
1
|
-
# Copyright (c) 2007-
|
|
1
|
+
# Copyright (c) 2007-2021 Vassilis Rizopoulos. All rights reserved.
|
|
2
|
+
|
|
2
3
|
require_relative 'framework'
|
|
3
4
|
|
|
4
5
|
module Rutema
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
##
|
|
7
|
+
# Module for the definition of classes for the parsing of test specifications
|
|
8
|
+
module Parsers
|
|
9
|
+
##
|
|
10
|
+
# Base class for parser implementations
|
|
11
|
+
#
|
|
12
|
+
# This class itself is not operational but only throws exceptions upon
|
|
13
|
+
# invocations of its +parse_*+ methods. Its sole purpose is to define a
|
|
14
|
+
# common interface for parser classes.
|
|
7
15
|
#
|
|
8
|
-
#
|
|
16
|
+
# Derived classes should implement at least the parse_specification and
|
|
17
|
+
# validate_configuration methods.
|
|
9
18
|
class SpecificationParser
|
|
19
|
+
##
|
|
20
|
+
# The Configuration instance passed to and used by the initializer
|
|
10
21
|
attr_reader :configuration
|
|
22
|
+
|
|
23
|
+
##
|
|
24
|
+
# Initialize a new instance internally storing and validating the passed
|
|
25
|
+
# Configuration instance
|
|
11
26
|
def initialize configuration
|
|
12
27
|
@configuration=configuration
|
|
13
28
|
@configuration||={}
|
|
14
29
|
validate_configuration
|
|
15
30
|
end
|
|
16
|
-
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
# Parse a test specification
|
|
34
|
+
#
|
|
35
|
+
# The passed argument can either be the path to a test specification file
|
|
36
|
+
# or the test specification itself.
|
|
17
37
|
def parse_specification param
|
|
18
38
|
raise ParserError,"not implemented. You should derive a parser implementation from SpecificationParser!"
|
|
19
39
|
end
|
|
20
|
-
|
|
40
|
+
|
|
41
|
+
##
|
|
42
|
+
# Parse a setup specification
|
|
43
|
+
#
|
|
44
|
+
# This calls #parse_specification by default.
|
|
21
45
|
def parse_setup param
|
|
22
46
|
parse_specification(param)
|
|
23
47
|
end
|
|
24
|
-
|
|
48
|
+
|
|
49
|
+
##
|
|
50
|
+
# Parse a teardown specification
|
|
51
|
+
#
|
|
52
|
+
# This calls #parse_specification by default.
|
|
25
53
|
def parse_teardown param
|
|
26
54
|
parse_specification(param)
|
|
27
55
|
end
|
|
28
|
-
|
|
56
|
+
|
|
57
|
+
##
|
|
58
|
+
# Validate the Configuration instance which is stored by the parser
|
|
59
|
+
# internally
|
|
29
60
|
#
|
|
30
|
-
#To avoid validating the configuration in element_* methods repeatedly, do all configuration validation here
|
|
61
|
+
# To avoid validating the configuration in element_* methods repeatedly, do all configuration validation here
|
|
31
62
|
def validate_configuration
|
|
32
63
|
end
|
|
33
64
|
end
|
|
34
65
|
end
|
|
35
|
-
end
|
|
66
|
+
end
|