testability-driver-qt-sut-plugin 1.1.1 → 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 (40) hide show
  1. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/behaviours/action.rb +7 -6
  2. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/behaviours/application.rb +190 -182
  3. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/behaviours/attribute.rb +2 -2
  4. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/behaviours/behaviour.rb +17 -17
  5. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/behaviours/configure_behaviour.rb +6 -6
  6. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/behaviours/events.rb +6 -6
  7. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/behaviours/find.rb +2 -2
  8. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/behaviours/fixture.rb +10 -14
  9. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/behaviours/fps.rb +7 -7
  10. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/behaviours/gesture.rb +387 -338
  11. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/behaviours/infologger.rb +177 -5
  12. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/behaviours/key_press.rb +5 -5
  13. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/behaviours/locale_db.rb +2 -1
  14. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/behaviours/method.rb +34 -7
  15. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/behaviours/multitouch.rb +4 -4
  16. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/behaviours/os.rb +4 -4
  17. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/behaviours/record.rb +6 -6
  18. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/behaviours/screen_capture.rb +8 -12
  19. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/behaviours/settings.rb +8 -8
  20. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/behaviours/sut.rb +171 -115
  21. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/behaviours/synchronization.rb +2 -2
  22. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/behaviours/treewidgetitemcolumn.rb +11 -7
  23. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/behaviours/type_text.rb +2 -2
  24. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/behaviours/view_item.rb +5 -5
  25. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/behaviours/webkit.rb +13 -13
  26. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/behaviours/widget.rb +54 -63
  27. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/commands/find_object.rb +45 -28
  28. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/commands/version.rb +34 -0
  29. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/commands/widget.rb +2 -2
  30. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/controllers/application.rb +174 -121
  31. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/controllers/find_object.rb +35 -26
  32. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/controllers/version.rb +56 -0
  33. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/plugin.rb +64 -16
  34. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/sut/adapter.rb +138 -40
  35. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/sut/communication.rb +91 -83
  36. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/util/find_object_generator.rb +222 -44
  37. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/util/message_composer.rb +176 -10
  38. data/lib/testability-driver-plugins/testability-driver-qt-sut-plugin/util/widget.rb +2 -2
  39. data/xml/behaviour/qt.xml +12 -0
  40. metadata +7 -5
@@ -21,137 +21,145 @@ module MobyController
21
21
 
22
22
  module QT
23
23
 
24
- module Comms
24
+ module Comms
25
25
 
26
- # Command types
27
- ERROR_MSG = 0
28
- VALID_MSG = 1
29
- OK_MESSAGE = "OK"
26
+ # Command types
27
+ ERROR_MSG = 0
28
+ VALID_MSG = 1
29
+ OK_MESSAGE = "OK"
30
30
 
31
- class Inflator
31
+ class Inflator
32
32
 
33
- def self.inflate( response )
34
- # strip 4 extra bytes written by qt compression, return empty string if no raw data found, otherwise inflate it
35
- ( raw_data = response[ 4 .. -1 ] ).empty? ? "" : Zlib::Inflate.inflate( raw_data )
36
- end
33
+ def self.inflate( response )
34
+ # strip 4 extra bytes written by qt compression, return empty string if no raw data found, otherwise inflate it
35
+ ( raw_data = response[ 4 .. -1 ] ).empty? ? "" : Zlib::Inflate.inflate( raw_data )
36
+ end
37
37
 
38
- # enable hooking for performance measurement & debug logging
39
- TDriver::Hooking.hook_methods( self ) if defined?( TDriver::Hooking )
38
+ # enable hooking for performance measurement & debug logging
39
+ TDriver::Hooking.hook_methods( self ) if defined?( TDriver::Hooking )
40
40
 
41
- end
41
+ end # Inflator
42
42
 
43
43
  # deprecated: see send_service_request#adapter.rb
44
- # Wrapper for protocol message.
45
- class QTResponse
44
+ # Wrapper for protocol message.
45
+ class QTResponse
46
46
 
47
- attr_accessor :msg_body, :flag, :crc, :message_id
47
+ attr_accessor :msg_body, :flag, :crc, :message_id
48
48
 
49
49
  # deprecated: see send_service_request#adapter.rb
50
- # Initialize QT Response.
51
- # == params
52
- # command_flag Indicator flad for message type (error or ok)
53
- # message_code 0 or an error code
54
- # msg_body Body of the message. For command a simple "OK" message for ui state the xml document as string
55
- # == returns
56
- def initialize( command_flag, msg_body, crc, message_id )
50
+ # Initialize QT Response.
51
+ # == params
52
+ # command_flag Indicator flad for message type (error or ok)
53
+ # message_code 0 or an error code
54
+ # msg_body Body of the message. For command a simple "OK" message for ui state the xml document as string
55
+ # == returns
56
+ def initialize( command_flag, msg_body, crc, message_id )
57
57
 
58
+ @flag, @msg_body, @crc, @message_id = command_flag, msg_body, crc, message_id
58
59
 
59
- @flag, @msg_body, @crc, @message_id = command_flag, msg_body, crc, message_id
60
-
61
- end
60
+ end
62
61
 
63
62
  # deprecated: see send_service_request#adapter.rb
64
- def validate_message( msg_id )
63
+ def validate_message( msg_id )
64
+
65
+ #check that response matches the request
66
+ if @message_id != msg_id
65
67
 
66
- #check that response matches the request
67
- if @message_id != msg_id
68
+ msg = "Response to request did not match: #{ @message_id.to_s.inspect }!=#{ msg_id.to_s.inspect }"
68
69
 
69
- $logger.log "fatal" , "Response to request did not match: \"#{@message_id}\"!=\"#{msg_id.to_s}\""
70
- $logger.log "fatal" , @msg_body
70
+ $logger.fatal msg
71
+
72
+ $logger.fatal @msg_body
71
73
 
72
- Kernel::raise RuntimeError.new("Response to request did not match: \"#{@message_id}\"!=\"#{msg_id.to_s}\"")
74
+ raise RuntimeError, msg
73
75
 
74
- end
75
-
76
- #raise error if error flag
77
- if @flag == Comms::ERROR_MSG
78
- if @msg_body =~ /The application with Id \d+ is no longer available/
79
- Kernel::raise MobyBase::ApplicationNotAvailableError.new( @msg_body)
80
- else
81
- Kernel::raise RuntimeError.new( @msg_body )
82
- end
76
+ end
77
+
78
+ #raise error if error flag
79
+ if @flag == Comms::ERROR_MSG
80
+
81
+ raise MobyBase::ApplicationNotAvailableError, @msg_body if @msg_body =~ /The application with Id \d+ is no longer available/
82
+
83
+ raise RuntimeError, @msg_body
84
+
83
85
  end
84
- end
85
86
 
86
- # enable hooking for performance measurement & debug logging
87
- TDriver::Hooking.hook_methods( self ) if defined?( TDriver::Hooking )
87
+ end
88
+
89
+ # enable hooking for performance measurement & debug logging
90
+ TDriver::Hooking.hook_methods( self ) if defined?( TDriver::Hooking )
91
+
92
+ end # QTResponse
88
93
 
89
- end #class
94
+ # Message generator for qt tas messages
95
+ class MessageGenerator
90
96
 
91
- # Message generator for qt tas messages
92
- class MessageGenerator
97
+ def self.generate( message_data, message_flag = VALID_MSG )
93
98
 
94
- def self.generate( message_data, message_flag = VALID_MSG )
99
+ MobyController::QT::Comms::QtMessage.new( message_flag, message_data )
95
100
 
96
- MobyController::QT::Comms::QtMessage.new( message_flag, message_data )
101
+ end
97
102
 
98
- end
103
+ # enable hooking for performance measurement & debug logging
104
+ TDriver::Hooking.hook_methods( self ) if defined?( TDriver::Hooking )
99
105
 
100
- # enable hooking for performance measurement & debug logging
101
- TDriver::Hooking.hook_methods( self ) if defined?( TDriver::Hooking )
106
+ end # MobyController
102
107
 
103
- end
108
+ class QtMessage
104
109
 
105
- class QtMessage
110
+ attr_reader :flag, :data, :crc, :compression, :size
106
111
 
107
- attr_reader :flag, :data, :crc, :compression, :size
108
- attr_accessor :message_id
112
+ attr_accessor :message_id
113
+
114
+ def initialize( message_flag, message_data )
109
115
 
110
- def initialize( message_flag, message_data )
111
116
  # message flag
112
117
  @flag = message_flag
113
118
 
114
119
  # no compression by default
115
120
  @compression = 1
116
121
 
122
+ @data = message_data
123
+
124
+ @size = @data.size
125
+
117
126
  # compress message body if size is greater than 1000 bytes
118
- deflate if ( @size = ( @data = message_data ).size ) > 1000
127
+ deflate if @size > 1000
119
128
 
120
129
  # calculate outgoing message crc; sent in message header to receiver for data validation
121
- @crc = CRC::Crc16.crc16_ibm( @data, 0xffff )
122
- end
130
+ @crc = TDriver::Checksum.crc16_ibm( @data )
131
+
132
+ end
123
133
 
124
- def make_binary_message( message_id )
134
+ def make_binary_message( message_id )
125
135
 
126
- [ @flag, @size, @crc, @compression, message_id, @data ].pack( 'CISCIa*' )
136
+ [ @flag, @size, @crc, @compression, message_id, @data ].pack( 'CISCIa*' )
127
137
 
128
- end
138
+ end
129
139
 
130
- def compression
131
- @compression
132
- end
140
+ def deflate
133
141
 
134
- def deflate
135
- @compression = 2
136
- #qUncompress required the data length at the beginning so append it
137
- #the bytes need to be arranged in the below method (see QByteArray::qUncompress)
138
- @data = [
139
- (@data.size & 0xff000000) >> 24, (@data.size & 0x00ff0000) >> 16,
140
- (@data.size & 0x0000ff00) >> 8, (@data.size & 0x000000ff),
141
- Zlib::Deflate.deflate( @data, 9)
142
- ].pack('C4a*')
142
+ @compression = 2
143
+
144
+ #@data = [ (@data.size & 0xff000000) >> 24, (@data.size & 0x00ff0000) >> 16, (@data.size & 0x0000ff00) >> 8, (@data.size & 0x000000ff), Zlib::Deflate.deflate( @data, 9 ) ].pack('C4a*')
145
+
146
+ data_size = @data.size
147
+
148
+ # qUncompress required the data length at the beginning so append it - the bytes need to be arranged in the below method (see QByteArray::qUncompress)
149
+ @data = [ (data_size & 0xff000000) >> 24, (data_size & 0x00ff0000) >> 16, (data_size & 0x0000ff00) >> 8, (data_size & 0x000000ff), Zlib::Deflate.deflate( @data, 9 ) ].pack('C4a*')
143
150
 
144
151
  # update data size
145
152
  @size = @data.size
146
-
147
- end
153
+
154
+ end
155
+
156
+ # enable hooking for performance measurement & debug logging
157
+ TDriver::Hooking.hook_methods( self ) if defined?( TDriver::Hooking )
148
158
 
149
- # enable hooking for performance measurement & debug logging
150
- TDriver::Hooking.hook_methods( self ) if defined?( TDriver::Hooking )
159
+ end # QtMessage
151
160
 
152
- end # class
161
+ end # Comms
153
162
 
154
- end # module Comms
163
+ end # QT
155
164
 
156
- end
157
- end
165
+ end # MobyController
@@ -20,85 +20,262 @@
20
20
  module MobyUtil
21
21
 
22
22
  module FindObjectGenerator
23
+
24
+ def generate_message
25
+
26
+ # get sut paramteres only once, store to local variable
27
+ sut_parameters = $parameters[ @_sut.id ]
28
+
29
+ filter_type = sut_parameters[ :filter_type, 'none' ]
30
+
31
+ if filter_type != 'none'
32
+
33
+ filters = {}
34
+
35
+ sut_parameters.if_found( :filter_properties ){ | key, value | filters[ 'filterProperties' ] = value }
36
+ sut_parameters.if_found( :plugin_blacklist ){ | key, value | filters[ 'pluginBlackList' ] = value }
37
+ sut_parameters.if_found( :plugin_whitelist ){ | key, value | filters[ 'pluginWhiteList' ] = value }
38
+
39
+ case filter_type
40
+
41
+ when 'dynamic'
42
+
43
+ value = MobyUtil::DynamicAttributeFilter.instance.filter_string
44
+
45
+ filters[ 'attributeWhiteList' ] = value unless value.blank?
46
+
47
+ when 'static'
48
+
49
+ sut_parameters.if_found( :attribute_blacklist ){ | key, value | filters[ 'attributeBlackList' ] = value }
50
+ sut_parameters.if_found( :attribute_whitelist ){ | key, value | filters[ 'attributeWhiteList' ] = value }
51
+
52
+ end
53
+
54
+ else
55
+
56
+ filters = {}
57
+
58
+ end
59
+
60
+ #xml = "<?xml version=\"1.0\"?>"
61
+
62
+ xml = "<TasCommands service=\"findObject\" #{ @_app_details.to_attributes }"
63
+
64
+ # pass checksum value if known from previous service request result
65
+ unless @_checksum.nil?
66
+
67
+ xml << " checksum=\"#{ @_checksum.to_s }\" "
68
+
69
+ end
70
+
71
+ unless @_params.empty?
72
+
73
+ # TasCommands close
74
+ xml << '><Target>'
75
+
76
+ # temp. objects xml fragment
77
+ objects = ""
78
+
79
+ # collect objects with attributes
80
+ @_params.reverse_each{ | parameters |
81
+
82
+ if parameters == @_params.last
83
+
84
+ objects = "<object #{ parameters.to_attributes } />"
85
+
86
+ else
87
+
88
+ objects = "<object #{ parameters.to_attributes }>#{ objects }</object>"
89
+
90
+ end
91
+
92
+ }
93
+
94
+ # add objects to xml
95
+ xml << objects
96
+
97
+ xml << '<Command name="findObject">'
98
+
99
+ filters.each{ | name, value | xml << "<param name=\"#{ name }\" value=\"#{ value }\" />" }
100
+
101
+ xml << '</Command></Target></TasCommands>'
102
+
103
+ else
104
+
105
+ # TasCommands close
106
+ xml << ' />'
107
+
108
+ end
109
+
110
+ xml
111
+
112
+ end
113
+
114
+ =begin
115
+ def generate_message
116
+
117
+ # get sut paramteres only once, store to local variable
118
+ sut_parameters = $parameters[ @_sut.id ]
119
+
120
+ filter_type = sut_parameters[ :filter_type, 'none' ]
23
121
 
24
- def generate_message
122
+ if filter_type != 'none'
123
+
124
+ filters = {}
125
+ value = nil
126
+ filters[ 'filterProperties' ] = value if ( value = sut_parameters[ :filter_properties, nil ] )
127
+ filters[ 'pluginBlackList' ] = value if ( value = sut_parameters[ :plugin_blacklist, nil ] )
128
+ filters[ 'pluginWhiteList' ] = value if ( value = sut_parameters[ :plugin_whitelist, nil ] )
129
+
130
+ case filter_type
131
+
132
+ when 'dynamic'
133
+
134
+ filters[ 'attributeWhiteList' ] = value if ( value = MobyUtil::DynamicAttributeFilter.instance.filter_string )
135
+
136
+ when 'static'
137
+
138
+ filters[ 'attributeBlackList' ] = value if ( value = sut_parameters[ :attribute_blacklist, nil ] )
139
+ filters[ 'attributeWhiteList' ] = value if ( value = sut_parameters[ :attribute_whitelist, nil ] )
140
+
141
+ end
142
+
143
+ else
144
+
145
+ filters = {}
146
+
147
+ end
148
+
149
+ #xml = "<?xml version=\"1.0\"?>"
150
+
151
+ xml = "<TasCommands service=\"findObject\" #{ @_app_details.to_attributes }"
152
+
153
+ unless @_params.empty?
154
+
155
+ # TasCommands close
156
+ xml << '><Target>'
157
+
158
+ # temp. objects xml fragment
159
+ objects = ""
160
+
161
+ # collect objects with attributes
162
+ @_params.reverse.each_with_index{ | parameters, index |
163
+
164
+ if index == 0
165
+
166
+ objects = "<object #{ parameters.to_attributes } />"
167
+
168
+ else
169
+
170
+ objects = "<object #{ parameters.to_attributes }>#{ objects }</object>"
171
+
172
+ end
173
+
174
+ }
175
+
176
+ # add objects to xml
177
+ xml << objects
178
+
179
+ xml << '<Command name="findObject">'
180
+
181
+ filters.each{ | name, value | xml << "<param name=\"#{ name }\" value=\"#{ value }\" />" }
182
+
183
+ xml << '</Command></Target></TasCommands>'
184
+
185
+ else
186
+
187
+ # TasCommands close
188
+ xml << ' />'
189
+
190
+ end
191
+
192
+ xml
193
+
194
+ end
195
+ =end
196
+
197
+ =begin
198
+ def generate_message
25
199
 
26
- filter_type = $parameters[ @_sut.id ][ :filter_type, 'none' ]
200
+ filter_type = $parameters[ @_sut.id ][ :filter_type, 'none' ]
27
201
 
28
- filters = make_params if filter_type == 'dynamic'
202
+ filters = make_params if filter_type == 'dynamic'
29
203
 
30
- builder = Nokogiri::XML::Builder.new do |xml|
204
+ builder = Nokogiri::XML::Builder.new do |xml|
31
205
 
32
- xml.TasCommands( ( @_app_details || {} ).merge( :service => "findObject") ) {
206
+ xml.TasCommands( ( @_app_details || {} ).merge( :service => "findObject") ) {
33
207
 
34
- xml.Target{
208
+ xml.Target{
35
209
 
36
- add_objects( xml, @_params )
210
+ add_objects( xml, @_params )
37
211
 
38
- xml.Command( :name => 'findObject' ){ filters.collect{ | name, value | xml.param( :name => name, :value => value ) } } if filter_type == 'dynamic'
212
+ xml.Command( :name => 'findObject' ){ filters.collect{ | name, value | xml.param( :name => name, :value => value ) } } if filter_type == 'dynamic'
39
213
 
40
- } if @_params and @_params.size > 0
214
+ } if @_params and @_params.size > 0
41
215
 
42
- }
216
+ }
43
217
 
44
- end
218
+ end
45
219
 
46
- builder.to_xml
220
+ builder.to_xml
47
221
 
48
- end
222
+ end
223
+
49
224
 
50
- private
225
+ private
51
226
 
52
- def add_objects( builder, params )
227
+ def add_objects( builder, params )
53
228
 
54
- parent = builder.parent
229
+ parent = builder.parent
55
230
 
56
- params.each{| objectParams | parent = create_object_node( builder, objectParams, parent ) }
231
+ params.each{| objectParams |
232
+
233
+ parent = create_object_node( builder, objectParams, parent )
234
+
235
+ }
57
236
 
58
- end
237
+ end
59
238
 
60
- def create_object_node( builder, params, parent )
239
+ def create_object_node( builder, params, parent )
61
240
 
62
- node = Nokogiri::XML::Node.new( 'object', builder.doc )
241
+ node = Nokogiri::XML::Node.new( 'object', builder.doc )
63
242
 
64
- params.keys.each{ | key | node[ key.to_s ] = params[ key ].to_s }
243
+ params.keys.each{ | key | node[ key.to_s ] = params[ key ].to_s }
65
244
 
66
- parent.add_child( node )
67
-
68
- end
245
+ parent.add_child( node )
69
246
 
247
+ end
70
248
 
71
- def make_params
249
+ def make_params
72
250
 
73
- params = {}
251
+ params = {}
74
252
 
75
- # get sut paramteres only once, store to local variable
76
- sut_parameters = $parameters[ @_sut.id ]
253
+ # get sut paramteres only once, store to local variable
254
+ sut_parameters = $parameters[ @_sut.id ]
77
255
 
78
- params[ 'filterProperties' ] = $last_parameter if sut_parameters[ :filter_properties, nil ]
79
- params[ 'pluginBlackList' ] = $last_parameter if sut_parameters[ :plugin_blacklist, nil ]
80
- params[ 'pluginWhiteList' ] = $last_parameter if sut_parameters[ :plugin_whitelist, nil ]
256
+ params[ 'filterProperties' ] = $last_parameter if sut_parameters[ :filter_properties, nil ]
257
+ params[ 'pluginBlackList' ] = $last_parameter if sut_parameters[ :plugin_blacklist, nil ]
258
+ params[ 'pluginWhiteList' ] = $last_parameter if sut_parameters[ :plugin_whitelist, nil ]
81
259
 
82
- case sut_parameters[ :filter_type, 'none' ]
260
+ case sut_parameters[ :filter_type, 'none' ]
83
261
 
84
- when 'dynamic'
85
-
86
- # updates the filter with the current backtrace file list
87
- #MobyUtil::DynamicAttributeFilter.instance.update_filter( caller( 0 ) )
262
+ when 'dynamic'
88
263
 
89
- white_list = MobyUtil::DynamicAttributeFilter.instance.filter_string
90
- params['attributeWhiteList'] = white_list if white_list
264
+ white_list = MobyUtil::DynamicAttributeFilter.instance.filter_string
265
+
266
+ params['attributeWhiteList'] = white_list if white_list
91
267
 
92
- when 'static'
268
+ when 'static'
93
269
 
94
- params['attributeBlackList'] = $last_parameter if sut_parameters[ :attribute_blacklist, nil ]
95
- params['attributeWhiteList'] = $last_parameter if sut_parameters[ :attribute_whitelist, nil ]
270
+ params['attributeBlackList'] = $last_parameter if sut_parameters[ :attribute_blacklist, nil ]
271
+ params['attributeWhiteList'] = $last_parameter if sut_parameters[ :attribute_whitelist, nil ]
96
272
 
97
- end
273
+ end
98
274
 
99
- params
275
+ params
100
276
 
101
- end
277
+ end
278
+ =end
102
279
 
103
280
  # enable hoo./base/test_object/factory.rb:king for performance measurement & debug logging
104
281
  TDriver::Hooking.hook_methods( self ) if defined?( TDriver::Hooking )
@@ -106,3 +283,4 @@ module MobyUtil
106
283
  end
107
284
 
108
285
  end
286
+