Initial, simple, unit-tested implementation of QMPClient.
This follows the README given earlier, at least in principle, but doesn't implement the entirety of QMP, by any stretch of the imagination. Notable by their absence are error responses, argument validation (for incoming and outgoing messsages of all types), and any visibility into qmp_capabilities. Also missing are integration tests.
This commit is contained in:
146
test/unit/qmp_client/test_api.rb
Normal file
146
test/unit/qmp_client/test_api.rb
Normal file
@@ -0,0 +1,146 @@
|
||||
require 'helper'
|
||||
require 'qmp_client/api'
|
||||
|
||||
module TestQMPClient
|
||||
|
||||
class TestAPI < QMPClientTestCase
|
||||
|
||||
include ::QMPClient
|
||||
|
||||
def setup
|
||||
super
|
||||
|
||||
@read_q = Queue.new
|
||||
@write_q = Queue.new
|
||||
@api = QMPClient::API.new(@read_q, @write_q)
|
||||
end
|
||||
|
||||
def written_message
|
||||
assert_doesnt_time_out(1) { @write_q.pop }
|
||||
end
|
||||
|
||||
def greeting_from_server
|
||||
@read_q.push(Messages::Greeting.new({}, "", []))
|
||||
end
|
||||
|
||||
def reply_from_server(request_id, return_value)
|
||||
@read_q.push(Messages::Reply.new(request_id, return_value))
|
||||
end
|
||||
|
||||
def event_from_server(event_name, data=nil)
|
||||
@read_q.push(Messages::Event.new(event_name, Time.now, data))
|
||||
end
|
||||
|
||||
def with_api(tps=1, &blk)
|
||||
@api.run(tps, false, &blk)
|
||||
end
|
||||
|
||||
def assert_reply(msg, rid, val = nil)
|
||||
assert_kind_of(Messages::Reply, msg)
|
||||
assert_equal(rid, msg.request_id)
|
||||
assert_equal(val, msg.return_value)
|
||||
end
|
||||
|
||||
def test_run
|
||||
@api.run(1, false) do |api|
|
||||
assert_equal(@api, api, "run doesn't yield API")
|
||||
end
|
||||
end
|
||||
|
||||
def test_wait_for_greeting
|
||||
running = false
|
||||
endq = Queue.new
|
||||
|
||||
greeting_from_server
|
||||
t = Thread.new do
|
||||
@api.run(1, true) do |api|
|
||||
running = true
|
||||
end
|
||||
end
|
||||
|
||||
msg = written_message
|
||||
assert_kind_of(Messages::Command, msg)
|
||||
assert_equal('qmp_capabilities', msg.name)
|
||||
reply_from_server(msg.request_id, {})
|
||||
assert_doesnt_time_out(1, "Waiting for run block to execute") { t.join }
|
||||
assert(running, "Run block was never executed")
|
||||
end
|
||||
|
||||
def test_query
|
||||
with_api do |api|
|
||||
rsp = nil
|
||||
api.query('foo') {|m| rsp = m }
|
||||
msg = written_message
|
||||
assert_kind_of(Messages::Query, msg)
|
||||
assert(msg.request_id)
|
||||
assert_equal('foo', msg.name)
|
||||
reply_from_server(msg.request_id, 'bar')
|
||||
sleep(0.1) until rsp
|
||||
assert_reply(rsp, msg.request_id, 'bar')
|
||||
end
|
||||
end
|
||||
|
||||
def test_sync_query
|
||||
with_api do |api|
|
||||
rsp = nil
|
||||
t = Thread.new { rsp = api.sync_query('foo') }
|
||||
|
||||
msg = written_message
|
||||
assert_kind_of(Messages::Query, msg)
|
||||
assert(msg.request_id)
|
||||
assert_equal('foo', msg.name)
|
||||
reply_from_server(msg.request_id, 'bar')
|
||||
t.join
|
||||
assert_reply(rsp, msg.request_id, 'bar')
|
||||
end
|
||||
end
|
||||
|
||||
def test_command
|
||||
with_api do |api|
|
||||
rsp = nil
|
||||
api.command('foo', {'arg1' => 'val1'}) {|m| rsp = m }
|
||||
msg = written_message
|
||||
assert_kind_of(Messages::Command, msg)
|
||||
assert(msg.request_id)
|
||||
assert_equal('foo', msg.name)
|
||||
assert_equal({'arg1' => 'val1'}, msg.arguments)
|
||||
reply_from_server(msg.request_id, 'bar')
|
||||
sleep(0.1) until rsp
|
||||
assert_reply(rsp, msg.request_id, 'bar')
|
||||
end
|
||||
end
|
||||
|
||||
def test_sync_command
|
||||
with_api do |api|
|
||||
rsp = nil
|
||||
t = Thread.new { rsp = api.sync_command('foo', {'a' => 'b'}) }
|
||||
|
||||
msg = written_message
|
||||
assert(msg.request_id)
|
||||
assert_equal('foo', msg.name)
|
||||
assert_equal({'a' => 'b'}, msg.arguments)
|
||||
reply_from_server(msg.request_id, 'bar')
|
||||
t.join
|
||||
assert_reply(rsp, msg.request_id, 'bar')
|
||||
end
|
||||
end
|
||||
|
||||
def test_on_event
|
||||
with_api do |api|
|
||||
rsp = nil
|
||||
api.on_event("FOO") {|e| rsp = e }
|
||||
event_from_server('FOO', {'a' => 'b'})
|
||||
sleep(0.1) until rsp
|
||||
assert_kind_of(Messages::Event, rsp)
|
||||
assert_equal('FOO', rsp.name)
|
||||
assert_kind_of(Time, rsp.time)
|
||||
assert_equal({'a' => 'b'}, rsp.data)
|
||||
end
|
||||
end
|
||||
|
||||
def test_wait
|
||||
skip("Not implemented")
|
||||
end
|
||||
|
||||
end
|
||||
end
|
84
test/unit/qmp_client/test_messages.rb
Normal file
84
test/unit/qmp_client/test_messages.rb
Normal file
@@ -0,0 +1,84 @@
|
||||
require 'helper'
|
||||
|
||||
require 'qmp_client/messages'
|
||||
|
||||
module TestQMPClient
|
||||
module TestMessages
|
||||
class TestModuleMethods < QMPClientTestCase
|
||||
include QMPClient
|
||||
|
||||
COMMAND_MSG = Messages::Command.new('00000001', 'foo', {'a' => 'b'})
|
||||
COMMAND_HSH = {'execute' => 'foo', 'arguments' => {'a'=>'b'}, 'id' => '00000001'}
|
||||
COMMAND_TXT = COMMAND_HSH.to_json
|
||||
|
||||
GREETING_MSG = Messages::Greeting.new(
|
||||
{'qemu' => {'micro' => 50, 'minor' => 13, 'major' => 0}},
|
||||
"", []
|
||||
)
|
||||
GREETING_HSH = {
|
||||
'QMP' => {
|
||||
'version' => {'qemu' => {'micro' => 50, 'minor' => 13, 'major' => 0}},
|
||||
'package' => "",
|
||||
},
|
||||
'capabilities' => []
|
||||
}
|
||||
GREETING_TXT = GREETING_HSH.to_json
|
||||
|
||||
ETIME = Time.now
|
||||
EVENT_MSG = Messages::Event.new('foo', ETIME, {'a' => 'b'})
|
||||
EVENT_HSH = {
|
||||
'event' => 'foo',
|
||||
'timestamp' => {'seconds' => ETIME.tv_sec, 'microseconds' => ETIME.tv_usec},
|
||||
'data' => {'a' => 'b'}
|
||||
}
|
||||
EVENT_TXT = EVENT_HSH.to_json
|
||||
|
||||
QUERY_MSG = Messages::Query.new('00000001', 'foo')
|
||||
QUERY_HSH = {
|
||||
'execute' => 'query-foo',
|
||||
'id' => '00000001'
|
||||
}
|
||||
QUERY_TXT = QUERY_HSH.to_json
|
||||
|
||||
REPLY_MSG = Messages::Reply.new('00000001', {'foo' => 'bar'})
|
||||
REPLY_HSH = {
|
||||
'id' => '00000001',
|
||||
'return' => {'foo' => 'bar'}
|
||||
}
|
||||
REPLY_TXT = REPLY_HSH.to_json
|
||||
|
||||
[
|
||||
[Messages::Command, COMMAND_MSG, COMMAND_HSH, COMMAND_TXT],
|
||||
[Messages::Greeting, GREETING_MSG, GREETING_HSH, GREETING_TXT],
|
||||
[Messages::Event, EVENT_MSG, EVENT_HSH, EVENT_TXT],
|
||||
[Messages::Query, QUERY_MSG, QUERY_HSH, QUERY_TXT],
|
||||
[Messages::Reply, REPLY_MSG, REPLY_HSH, REPLY_TXT]
|
||||
].each do |kls, msg_instance, msg_hash, msg_json|
|
||||
|
||||
t = kls.to_s.split("::")[-1].downcase
|
||||
|
||||
define_method("test_serialise_#{t}") do
|
||||
data = Messages::serialise(msg_instance)
|
||||
assert_kind_of(String, data)
|
||||
rsp = JSON::parse(data) # can't compare the strings directly
|
||||
assert_equal(msg_hash, rsp)
|
||||
end
|
||||
|
||||
define_method("test_deserialise_#{t}") do
|
||||
created = Messages::deserialise(msg_json)
|
||||
assert_kind_of(kls, created)
|
||||
assert_equal(msg_hash, created.to_hash)
|
||||
assert_equal(msg_instance, created)
|
||||
end
|
||||
|
||||
define_method("test_#{t}_to_hash_and_build") do
|
||||
created = kls.build(msg_hash)
|
||||
assert_equal(msg_hash, created.to_hash)
|
||||
assert_equal(msg_instance, created)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
82
test/unit/test_qmp_client.rb
Normal file
82
test/unit/test_qmp_client.rb
Normal file
@@ -0,0 +1,82 @@
|
||||
require 'helper'
|
||||
require 'qmp_client'
|
||||
|
||||
module TestQMPClient
|
||||
class TestModuleMethods < BaseTestCase
|
||||
|
||||
include QMPClient
|
||||
|
||||
def test_require_loads_main_constants
|
||||
assert(defined?(::QMPClient), "QMPClient not defined")
|
||||
assert(defined?(::QMPClient::API), "QMPClient::API not defined")
|
||||
assert(defined?(::QMPClient::Connectors), "Connectors not defined")
|
||||
assert(defined?(::QMPClient::Connectors::Socket), "Socket connector not defined")
|
||||
assert(defined?(::QMPClient::Connectors::WriteProxy), "WriteProxy not defined")
|
||||
assert(defined?(::QMPClient::Messages), "Messages module not defined")
|
||||
assert(defined?(::QMPClient::Messages::Query), "Query message not defined")
|
||||
assert(defined?(::QMPClient::Messages::Command), "Command message not defined")
|
||||
assert(defined?(::QMPClient::Messages::Event), "Event message not defined")
|
||||
assert(defined?(::QMPClient::Messages::Reply), "Greeting message not defined")
|
||||
end
|
||||
|
||||
def expects_socket_run(rio, wio=nil)
|
||||
rq, wq = [Queue.new, Queue.new]
|
||||
Connectors::Socket.any_instance.expects(:run).with(rio, wio).
|
||||
once.yields(rq, wq)
|
||||
API.expects(:run).with(rq, wq).once.yields("(mock-api)")
|
||||
end
|
||||
|
||||
def test_connect_tcp
|
||||
cargs = ["127.0.0.1", 4440, "127.0.0.1", 40000]
|
||||
mock_sock = mock("(tcp-socket)")
|
||||
mock_sock.stubs(:closed? => false, :close => true)
|
||||
TCPSocket.expects(:connect).with(*cargs).once.returns(mock_sock)
|
||||
expects_socket_run(mock_sock)
|
||||
|
||||
runs_api_block = false
|
||||
QMPClient::connect_tcp(*cargs) do |api|
|
||||
runs_api_block = true
|
||||
end
|
||||
|
||||
assert(runs_api_block, "API not yielded by connect_tcp")
|
||||
end
|
||||
|
||||
def test_connect_unix
|
||||
mock_sock = mock("(unix-socket)")
|
||||
mock_sock.stubs(:closed? => false, :close => true)
|
||||
UNIXSocket.expects(:connect).with("/tmp/test.sock").once.returns(mock_sock)
|
||||
|
||||
expects_socket_run(mock_sock)
|
||||
|
||||
runs_api_block = false
|
||||
QMPClient::connect_unix("/tmp/test.sock") do |api|
|
||||
runs_api_block = true
|
||||
end
|
||||
|
||||
assert(runs_api_block, "API not yielded by connect_unix")
|
||||
end
|
||||
|
||||
def test_connect_socket
|
||||
rs, ws = [mock("(read-socket)"), mock("(write-socket)")]
|
||||
expects_socket_run(rs, ws)
|
||||
|
||||
runs_api_block = false
|
||||
QMPClient::connect_socket(rs, ws) do |api|
|
||||
runs_api_block = true
|
||||
end
|
||||
|
||||
assert(runs_api_block, "API not yielded by connect_socket")
|
||||
|
||||
# If we pass in just one socket, it's used for both read and write
|
||||
rw = mock("(rw-socket)")
|
||||
runs_api_block = false
|
||||
expects_socket_run(rw)
|
||||
|
||||
QMPClient::connect_socket(rw) do
|
||||
runs_api_block = true
|
||||
end
|
||||
assert(runs_api_block, "API not yielded by connect_socket with one sock")
|
||||
end
|
||||
|
||||
end
|
||||
end
|
Reference in New Issue
Block a user