Created
August 20, 2011 08:12
-
-
Save quad/1158837 to your computer and use it in GitHub Desktop.
A spec for plaque
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'json' | |
require 'socket' | |
require 'timeout' | |
TIMEOUT = 2 | |
RSpec::Matchers.define :respond_with do |regexp| | |
match do |io| | |
begin | |
Timeout::timeout(TIMEOUT) { @message = io.readline } | |
rescue Timeout::Error | |
false | |
else | |
@message =~ regexp | |
end | |
end | |
failure_message_for_should do |io| | |
if @message | |
"Expected message matching #{regexp}, instead got: #{@message}" | |
else | |
"Timed out waiting for #{regexp}" | |
end | |
end | |
end | |
RSpec::Matchers.define :receive do |hash| | |
match do |socket| | |
begin | |
Timeout::timeout(TIMEOUT) { @message, addr = socket.recvfrom 65536 } | |
rescue Timeout::Error | |
false | |
else | |
JSON.parse(@message) == hash | |
end | |
end | |
failure_message_for_should do |socket| | |
if @message | |
"Expected message matching #{hash.inspect}, instead got #{@message}" | |
else | |
"Timed out waiting for #{hash.inspect}" | |
end | |
end | |
end | |
RSpec::Matchers.define :find do |node_id| | |
match do |io| | |
start = Time.now | |
until Time.now > start + 5 | |
io.puts "FIND #{node_id}" | |
io.should respond_with FIND_FOUND | |
@nodes = [] | |
begin | |
Timeout::timeout(TIMEOUT) do | |
# TODO: Check for the trailing "." | |
while io.readline =~ FIND_NODE | |
@nodes << $1 | |
end | |
end | |
rescue Timeout::Error | |
break | |
end | |
end | |
@nodes.include? node_id | |
end | |
failure_message_for_should do |io| | |
if @nodes | |
"Expected #{node_id.inspect} in #{@nodes.inspect}" | |
else | |
"Timed out waiting for FIND(#{node_id}) reply" | |
end | |
end | |
end | |
RSpec::Matchers.define :timeout do | |
match do |given_proc| | |
begin | |
Timeout::timeout(TIMEOUT) do | |
given_proc.call | |
end | |
rescue Timeout::Error | |
true | |
else | |
false | |
end | |
end | |
failure_message_for_should { "Didn't timeout" } | |
end | |
def plaque &block | |
IO.popen './plaque', 'w+', &block | |
end | |
HELLO = /^220 ([0-9A-Fa-f]{40}) ([.0-9]+) (\d+)/ | |
PING_OK = /^230/ | |
FIND_FOUND = /^240/ | |
FIND_NODE = /^([0-9A-Fa-f]{40}) ([.0-9]+) (\d+)/ | |
MESSAGE = /^350 ([0-9A-Fa-f]{40}) ([.0-9]+) (\d+)/ | |
SEND_READY = // | |
WAT = /^500/ | |
ID = 'ffffffffffffffffffffffffffffffffffffffff' | |
describe 'plaque' do | |
let(:socket) { UDPSocket.new } | |
let(:net) { socket.tap { |s| s.connect @ip, @port } } | |
subject { @plaque = plaque } | |
after { subject.close } | |
def say msg | |
subject.puts msg | |
end | |
def send obj, opts={} | |
if addr = opts[:to] | |
host, port = addr[3], addr[1] | |
socket.send obj.to_json, 0, host, port | |
else | |
net.send obj.to_json, 0 | |
end | |
end | |
def recv | |
message, addr = socket.recvfrom 65536 | |
return JSON.parse(message), addr | |
end | |
it { should respond_with HELLO } | |
it 'should reuse its node ID between sessions' | |
context "when it's running" do | |
before do | |
subject.should respond_with HELLO | |
@node_id, @ip, @port = $1, $2, $3.to_i | |
end | |
describe '(ping)' do | |
it 'should respond to a ping' do | |
send :t => 'abc123', :id => ID | |
socket.should receive 't' => 'abc123', 'id' => $1, 'r' => true | |
end | |
context 'when listening' do | |
before do | |
socket.bind '127.0.0.1', 0 | |
net, @socket_port, name, @socket_addr = socket.addr | |
end | |
it 'should ping on command' do | |
say "PING #{@socket_addr} #{@socket_port}" | |
should respond_with PING_OK | |
response, addr = recv | |
response.should include 't' | |
response['id'].should == @node_id | |
end | |
it 'should not respond to a pong' do | |
say "PING #{@socket_addr} #{@socket_port}" | |
response, addr = recv | |
response['id'] = ID | |
send response, :to => addr | |
expect { recv }.to timeout | |
end | |
it "should generate unique transaction IDs" | |
end | |
[ | |
['256.256.256.256', 0], | |
['1.2.3.4', 99999], | |
['', ''] | |
].each do |addr, port| | |
it "should reject an invalid ping command (#{addr}:#{port})" do | |
say "PING #{addr} #{port}" | |
should respond_with WAT | |
end | |
end | |
end | |
describe '(find)' do | |
it 'should respond to a find node query' do | |
send :t => 'abc123', :target => ID | |
socket.should receive 't' => 'abc123', 'nodes' => [] | |
end | |
context "when a second plaque is connected" do | |
before do | |
@second = plaque | |
@second.should respond_with HELLO | |
@second_node_id, @second_ip, @second_port = $1, $2, $3.to_i | |
# TODO: This is a hack. How can we improve? | |
@second_ip = '127.0.0.1' if @second_ip == '0.0.0.0' | |
say "PING #{@second_ip} #{@second_port}" | |
should respond_with PING_OK | |
end | |
after { @second.close } | |
it 'should respond to a find node query with results' do | |
send :t => 'abc123', :target => @second_node_id | |
socket.should receive 't' => 'abc123', | |
'nodes' => [{'id' => @second_node_id, | |
'ip' => @second_ip, | |
'port' => @second_port}] | |
end | |
it 'should find nodes on command' do | |
should find @second_node_id | |
end | |
end | |
it 'should reject an invalid find command' do | |
say 'FIND abc' | |
should respond_with WAT | |
end | |
end | |
describe '(send)' do | |
it 'should reject an invalid send command' do | |
say 'SEND abc' | |
should respond_with WAT | |
end | |
context 'when listening' do | |
before do | |
socket.bind '127.0.0.1', 0 | |
net, @socket_port, name, @socket_addr = socket.addr | |
end | |
it 'should send a message' do | |
say "SEND #{@socket_addr} #{@socket_port}" | |
should respond_with SEND_READY | |
say 'abc123' | |
say '.' | |
say respond_with SEND_SENT | |
response, addr = recv | |
response.should include 't' | |
response['id'].should == @node_id | |
response['m'].should == 'abc123' | |
end | |
end | |
it 'should receive a message' do | |
send :t => 'abc123', :id => ID, :m => 'abc123' | |
should respond_with MESSAGE | |
$1.should == 'abc123' | |
should respond_with 'abc123' | |
should respond_with '.' | |
end | |
end | |
it 'should route messsages between instances' do | |
plaque do |second| | |
second.should respond_with HELLO | |
second_node_id, second_ip, second_port = $1, $2, $3.to_i | |
say "PING #{second_ip} #{second_port}" | |
should respond_with PING_OK | |
should find second_node_id | |
second.should find @node_id | |
say "SEND #{second_ip} #{second_port}" | |
should respond_with SEND_READY | |
say 'abc123' | |
say '.' | |
say respond_with SEND_SENT | |
second.should respond_with MESSAGE | |
second.should respond_with 'abc123' | |
second.should respond_with '.' | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment