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:
Nick Thomas
2011-11-13 18:36:51 +00:00
parent 7777e5cacb
commit 51175ddf56
13 changed files with 833 additions and 1 deletions

View 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

View 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

View 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