Class: MobyController::QT::SutAdapter

Inherits:
SutAdapter
  • Object
show all
Defined in:
lib/testability-driver-plugins/testability-driver-qt-sut-plugin/sut/adapter.rb

Overview

Sut adapter that used TCP/IP connections to send and receive data from QT side.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(sut_id, receive_timeout = 25, send_timeout = 25, connect_timeout = 25) ⇒ SutAdapter

TODO: better way to set the host and port parameters

Initialize the tcp adapter for communicating with the device. Communication is done using two tcp channels one form commanding the device and one for receiving ui state data. UI state data receivin is done in a seprate thread so it is good once usage is complete the shutdown_comms is called

params

sut_id id for the sut so that client details can be fetched from params



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
101
102
103
104
105
106
107
108
109
# File 'lib/testability-driver-plugins/testability-driver-qt-sut-plugin/sut/adapter.rb', line 52

def initialize( sut_id, receive_timeout = 25, send_timeout = 25, connect_timeout = 25 )

  # reset socket
  @socket = nil

  # connection state is false by default
  @connected = false

  # store sut id
  @sut_id = sut_id

  # reset hooks - no hooks by default
  @hooks = {}

  # reset sent/received bytes and packets counters
  @socket_received_bytes = 0
  @socket_sent_bytes = 0

  @socket_received_packets = 0
  @socket_sent_packets = 0

  # set timeouts
  @socket_read_timeout = receive_timeout
  @socket_write_timeout = send_timeout
  @socket_connect_timeout = connect_timeout

  # randomized value for initial message packet counter
  @counter = rand( 1000 )

  # optimization - use local variables for less AST lookups
  @tcp_socket_select_method = TCPSocket.method( :select )

  @tdriver_checksum_crc16_ibm_method = TDriver::Checksum.method( :crc16_ibm )

  # retrieve sut configuration
  _sut_parameters = $parameters[ @sut_id ]

  # determine which inflate method to use
  if _sut_parameters[ :win_native, false ].to_s.true?

    @inflate_method = method( :inflate_windows_native )

  else

    @inflate_method = method( :inflate )

  end

  # default size 1kb
  @deflate_minimum_size = _sut_parameters[ :io_deflate_minimum_size_in_bytes, 1024 ].to_i

  # enabled by default - deflate outgoing service request if size > deflate_minimum_size
  @deflate_service_request = _sut_parameters[ :io_deflate_service_request, true ].true? 

  # retrieve default compression level - best compression by default
  @deflate_compression_level = _sut_parameters[ :io_deflate_compression_level, 9 ].to_i

end

Instance Attribute Details

#deflate_compression_levelObject

Returns the value of attribute deflate_compression_level.



35
36
37
# File 'lib/testability-driver-plugins/testability-driver-qt-sut-plugin/sut/adapter.rb', line 35

def deflate_compression_level
  @deflate_compression_level
end

#deflate_minimum_sizeObject

Returns the value of attribute deflate_minimum_size.



35
36
37
# File 'lib/testability-driver-plugins/testability-driver-qt-sut-plugin/sut/adapter.rb', line 35

def deflate_minimum_size
  @deflate_minimum_size
end

#deflate_service_requestObject

Returns the value of attribute deflate_service_request.



35
36
37
# File 'lib/testability-driver-plugins/testability-driver-qt-sut-plugin/sut/adapter.rb', line 35

def deflate_service_request
  @deflate_service_request
end

#socket_connect_timeoutObject

Returns the value of attribute socket_connect_timeout.



35
36
37
# File 'lib/testability-driver-plugins/testability-driver-qt-sut-plugin/sut/adapter.rb', line 35

def socket_connect_timeout
  @socket_connect_timeout
end

#socket_read_timeoutObject

Returns the value of attribute socket_read_timeout.



35
36
37
# File 'lib/testability-driver-plugins/testability-driver-qt-sut-plugin/sut/adapter.rb', line 35

def socket_read_timeout
  @socket_read_timeout
end

#socket_received_bytesObject (readonly)

Returns the value of attribute socket_received_bytes.



27
28
29
# File 'lib/testability-driver-plugins/testability-driver-qt-sut-plugin/sut/adapter.rb', line 27

def socket_received_bytes
  @socket_received_bytes
end

#socket_received_packetsObject (readonly)

Returns the value of attribute socket_received_packets.



27
28
29
# File 'lib/testability-driver-plugins/testability-driver-qt-sut-plugin/sut/adapter.rb', line 27

def socket_received_packets
  @socket_received_packets
end

#socket_sent_bytesObject (readonly)

Returns the value of attribute socket_sent_bytes.



27
28
29
# File 'lib/testability-driver-plugins/testability-driver-qt-sut-plugin/sut/adapter.rb', line 27

def socket_sent_bytes
  @socket_sent_bytes
end

#socket_sent_packetsObject (readonly)

Returns the value of attribute socket_sent_packets.



27
28
29
# File 'lib/testability-driver-plugins/testability-driver-qt-sut-plugin/sut/adapter.rb', line 27

def socket_sent_packets
  @socket_sent_packets
end

#socket_write_timeoutObject

Returns the value of attribute socket_write_timeout.



35
36
37
# File 'lib/testability-driver-plugins/testability-driver-qt-sut-plugin/sut/adapter.rb', line 35

def socket_write_timeout
  @socket_write_timeout
end

#sut_idObject (readonly)

Returns the value of attribute sut_id.



27
28
29
# File 'lib/testability-driver-plugins/testability-driver-qt-sut-plugin/sut/adapter.rb', line 27

def sut_id
  @sut_id
end

Instance Method Details

#append_command(node_list) ⇒ Object

TODO: document me



211
212
213
214
215
216
217
218
219
# File 'lib/testability-driver-plugins/testability-driver-qt-sut-plugin/sut/adapter.rb', line 211

def append_command( node_list )

  node_list.each { | child | 

    @_builder.doc.root.add_child( child )

  }

end

#connect(id = nil) ⇒ Object

TODO: document me



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
# File 'lib/testability-driver-plugins/testability-driver-qt-sut-plugin/sut/adapter.rb', line 148

def connect( id = nil )

  id ||= @sut_id

  sut_parameters = $parameters[ id, {} ]

  begin

    # retrieve ip and verify that value is not empty or nil
    ip = sut_parameters[ :qttas_server_ip, nil ].not_blank( 'Connection failure; QTTAS server IP not defined in SUT configuration' ).to_s

    # retrieve port and verify that value is not empty or nil
    port = sut_parameters[ :qttas_server_port, nil ].not_blank( 'Connection failure; QTTAS server port not defined in SUT configuration' ).to_i

    # executes the code block before openning the connection
    execute_hook( 'before_connect', id, ip, port ) if hooked?( 'before_connect' ) 

    # open tcp/ip connection
    # Using ruby TCPSocket this way will utilize the underlying kernel to do the timeout, which by default is too long (In my tests, on ubuntu 10.10, TCPSocket.open
    # will wait for exactly 380 seconds before throwing exception which is *FAR* too long ..
    @socket = TCPSocket.open( ip, port )


    # open tcp/ip connectio
    ## The block will actually double the time, so halve it. Actual timeout will +1 if it's an odd number
#          @socket = timeout_capable_socket_opener(ip,port,(@socket_connect_timeout.to_i / 2.0).ceil)

    # set connected status to true
    @connected = true

    # communication authentication etc can be done here
    execute_hook( 'after_connect', id, ip, port, @socket ) if hooked?( 'after_connect' ) 

  rescue

    execute_hook( 'connection_failed', id, ip, port, $! ) if hooked?( 'connection_failed' ) 
    #If reporter active report connetion error          
    
    raise IOError, "Connection failure; verify that QTTAS server is up and running at #{ ip }:#{ port }.\n Nested exception: #{ $!.message }"

  end

  true

end

#connected?Boolean

Returns:

  • (Boolean)


243
244
245
246
247
# File 'lib/testability-driver-plugins/testability-driver-qt-sut-plugin/sut/adapter.rb', line 243

def connected?

  @connected

end

#disconnectObject

TODO: document me



112
113
114
115
116
117
118
119
# File 'lib/testability-driver-plugins/testability-driver-qt-sut-plugin/sut/adapter.rb', line 112

def disconnect

  # disconnect socket only if connected
  @socket.close if @connected

  @connected = false

end

#group?Boolean

TODO: document me

Returns:

  • (Boolean)


195
196
197
198
199
# File 'lib/testability-driver-plugins/testability-driver-qt-sut-plugin/sut/adapter.rb', line 195

def group?

  @_group

end

#send_grouped_requestObject

Sends a grouped command message to the server. Sets group to false and nils the builder to prevent future behviours of being grouped (unless so wanted)

returns

the amout of commands grouped (and send)



225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/testability-driver-plugins/testability-driver-qt-sut-plugin/sut/adapter.rb', line 225

def send_grouped_request

  @_group = false

  size = @_builder.doc.root.children.size

  send_service_request(

    Comms::MessageGenerator.generate( @_builder.to_xml )

  )

  @_builder = nil

  size

end

#send_service_request(message, return_checksum = false) ⇒ Object

Send the message to the qt server

If there is no exception propagated the send to the device was successful

params

message

message in qttas protocol format

returns

the response body



255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# File 'lib/testability-driver-plugins/testability-driver-qt-sut-plugin/sut/adapter.rb', line 255

def send_service_request( message, return_checksum = false )

  read_message_id = 0

  header = nil

  body = nil

  crc = nil

  connect if !@connected

  # increase message count
  @counter += 1

  # set request message id
  message.message_id = @counter

  # deflate message body
  if @deflate_service_request == true

    # do not deflate messages below 1kb
    message.deflate( @deflate_compression_level ) unless message.size < @deflate_minimum_size

  end

  # generate binary message to be sent to socket
  binary_message = message.make_binary_message( @counter )

  # write request message to socket
  write_socket( binary_message )

  until read_message_id == @counter
  
    # read message header from socket, unpack string to array
    # header[ 0 ] = command_flag
    # header[ 1 ] = body_size
    # header[ 2 ] = crc
    # header[ 3 ] = compression_flag
    # header[ 4 ] = message_id
    header = read_socket( 12 ).unpack( 'CISCI' )

    # read message body from socket
    body = read_socket( header[ 1 ] )

    # calculate body crc16 checksum
    crc = @tdriver_checksum_crc16_ibm_method.call( body )

    # read the message body and compare crc checksum
    raise IOError, "CRC checksum did not match, response message body is corrupted! (#{ crc } != #{ header[ 2 ] })" if crc != header[ 2 ]
    
    # validate response message; check that response message id matches the request
    # if smaller than expected try to read the next message but if bigger raise error
    read_message_id = header[ 4 ]

    if read_message_id < @counter

      $logger.warning "Response to request did not match: \"#{ header[ 4 ].to_s }\"<\"#{ @counter.to_s }\""

    elsif read_message_id > @counter

      $logger.fatal "Response to request did not match: \"#{ header[ 4 ].to_s }\">\"#{ @counter.to_s }\""

      # save to file?
      $logger.fatal body

      raise RuntimeError, "Response to request did not match: \"#{ header[ 4 ].to_s }\"!=\"#{ @counter.to_s }\""

    end
    
  end

  # inflate the message body if compressed
  body = @inflate_method.call( body ) if ( header[ 3 ] == 2 )

  # raise exception if messages error flag is set
  # Flag statuses:
  #   0 -> ERROR_MSG
  #   1 -> VALID_MSG
  #   2 -> OK_MESSAGE
  if header[ 0 ] == 0

    if body =~ /The application with Id \d+ is no longer available/

      raise MobyBase::ApplicationNotAvailableError, body

    else

      raise RuntimeError, body

    end

  end

  # return the body and checksum if required
  return_checksum ? [ body, body.hash ] : body

end

#set_message_builder(builder) ⇒ Object

Set the document builder for the grouped behaviour message.



202
203
204
205
206
207
208
# File 'lib/testability-driver-plugins/testability-driver-qt-sut-plugin/sut/adapter.rb', line 202

def set_message_builder( builder )

  @_group = true

  @_builder = builder

end

#timeout_capable_socket_opener(ip, port, timeout = nil) ⇒ Object



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
# File 'lib/testability-driver-plugins/testability-driver-qt-sut-plugin/sut/adapter.rb', line 121

def timeout_capable_socket_opener(ip,port,timeout=nil)
  addr = Socket.getaddrinfo(ip, nil)
  sock = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
  if timeout
     secs = Integer(timeout)
     usecs = Integer((timeout - secs) * 1_000_000)
     optval = [secs, usecs].pack("l_2")
     ## actual timeout gets triggered after 2 times of "timeout" value, most likely because my patch applies timeout to read AND write ..
     sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
     sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
     ## also, worth checking if there's actually some Socket::SO_*  that applies the timeout to connection forming ..
  end
  begin
     sock.connect_nonblock(Socket.pack_sockaddr_in(port, addr[0][3]))
  rescue Errno::EINPROGRESS
     resp = IO.select([sock],nil, nil, timeout.to_i)
     begin
        sock.connect_nonblock(Socket.pack_sockaddr_in(port, addr[0][3]))
     rescue Errno::EISCONN
     end
  end
  sock ## Its also worth noting that if we set RCV AND SNDTIMEOT to some value when checking for established socket,
       ## it might make sense to set the defaults values back again so that only during the connection, timeout is
       ## different..
end