diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..297fea5 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +language: ruby +rvm: + - 2.5.1 diff --git a/lib/melon/drb.rb b/lib/melon/drb.rb index 39817e7..903f58d 100644 --- a/lib/melon/drb.rb +++ b/lib/melon/drb.rb @@ -7,20 +7,18 @@ module Melon def self.with_drb local_storage: Melon::LocalStorage.new, host: "localhost", port: 8484 @drb_servers << Melon::DRb::StorageServer.new(local_storage, host: host, port: port) - Melon::Paradigm.new(local_storage) + Melon::DRb::Paradigm.new(local_storage) end def self.stop_drb @drb_servers.each(&:stop) end - def self.servers - @drb_servers - end - - class Paradigm - def add_remote host: "localhost", port: 8484 - self.add_server Melon::DRb::RemoteStorage.new(host: host, port: port) + module DRb + class Paradigm < ::Melon::Paradigm + def add_remote host: "localhost", port: 8484 + self.add_server Melon::DRb::RemoteStorage.new(host: host, port: port) + end end end end diff --git a/lib/melon/udp.rb b/lib/melon/udp.rb new file mode 100644 index 0000000..6d4a83c --- /dev/null +++ b/lib/melon/udp.rb @@ -0,0 +1,24 @@ +require_relative '../melon' +require_relative 'udp/remote_storage' +require_relative 'udp/storage_server' + +module Melon + @udp_servers = [] + + def self.with_udp local_storage: Melon::LocalStorage.new, host: "localhost", port: 8484 + @udp_servers << Melon::UDP::StorageServer.new(local_storage, host: host, port: port) + Melon::UDP::Paradigm.new(local_storage) + end + + def self.stop_udp + @udp_servers.each(&:stop) + end + + module UDP + class Paradigm < Melon::Paradigm + def add_remote host: "localhost", port: 8484 + self.add_server Melon::UDP::RemoteStorage.new(host: host, port: port) + end + end + end +end diff --git a/lib/melon/udp/remote_storage.rb b/lib/melon/udp/remote_storage.rb new file mode 100644 index 0000000..5fa14c2 --- /dev/null +++ b/lib/melon/udp/remote_storage.rb @@ -0,0 +1,42 @@ +require 'socket' + +module Melon + module UDP + class RemoteStorage + def initialize host: "localhost", port: 8484 + @socket = UDPSocket.new + @socket.connect host, port + end + + def send_msg action, template, read_msgs = nil + msg = { + action: action, + template: template + } + + msg[:read_msgs] = read_msgs if read_msgs + + @socket.send Marshal.dump(msg), 0 + + reply, _ = @socket.recvfrom(6500) + Marshal.load reply + end + + def find_unread template, read_msgs + send_msg :find_unread, template, read_msgs + end + + def find_all_unread template, read_msgs + send_msg :find_all_unread, template, read_msgs + end + + def find_and_take template + send_msg :find_and_take, template + end + + def take_all template + send_msg :take_all, template + end + end + end +end diff --git a/lib/melon/udp/storage_server.rb b/lib/melon/udp/storage_server.rb new file mode 100644 index 0000000..5f66390 --- /dev/null +++ b/lib/melon/udp/storage_server.rb @@ -0,0 +1,68 @@ +require 'socket' + +Thread.abort_on_exception = true + +module Melon + module UDP + class StorageServer + def initialize storage, host: "localhost", port: 8484 + @storage = storage + + begin + @thread = Thread.new do + Socket.udp_server_loop(host, port) do |msg, sender| + handle_message msg, sender + end + end + rescue Errno::EADDRINUSE + port += 1 + retry + end + + warn "Started Melon server on #{host} #{port}" + end + + def stop + @thread.kill + end + + def handle_message msg, sender + m = Marshal.load msg + template = m[:template] + read_msgs = m[:read_msgs] + + reply = case m[:action] + when :find_unread + find_unread template, read_msgs + when :find_all_unread + find_all_unread template, read_msgs + when :find_and_take + find_and_take template + when :take_all + take_all template + else + warn "Unknown message: #{m.inspect}" + { error: "Unknown message: #{m.inspect}" } + end + + sender.reply Marshal.dump(reply) + end + + def find_unread template, read_msgs + @storage.find_unread template, read_msgs + end + + def find_all_unread template, read_msgs + @storage.find_all_unread template, read_msgs + end + + def find_and_take template + @storage.find_and_take template + end + + def take_all template + @storage.take_all template + end + end + end +end diff --git a/test/test.rb b/test/test.rb deleted file mode 100644 index 8ea3e0f..0000000 --- a/test/test.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'minitest' -require 'minitest/autorun' -require 'minitest/pride' -require_relative '../lib/melon' diff --git a/test/test_drb_melon.rb b/test/test_drb_melon.rb index b52d656..b61bf3d 100644 --- a/test/test_drb_melon.rb +++ b/test/test_drb_melon.rb @@ -1,81 +1,19 @@ +require_relative 'test_helper' require 'melon/drb' class TestDRbMelon < Minitest::Test + include CoreMelonTests + def teardown Melon.stop_drb ::DRb.stop_service end - def test_sanity - assert_kind_of Melon::Paradigm, Melon.with_drb - end - - def test_store_take - m1 = Melon.with_drb port: 8484 - m2 = Melon.with_drb port: 8585 - - m2.add_remote port: 8484 - m1.add_remote port: 8585 - - m1.store ["hello", "world"] - m1.store ["hello", "world"] - msg1 = m2.take [String, "world"] - msg2 = m2.take [String, "world"] - msg3 = m2.take [String, "world"], false - - assert_equal ["hello", "world"], msg1 - assert_equal ["hello", "world"], msg2 - assert_nil msg3 - end - - def test_store_take_all - m1 = Melon.with_drb port: 8489 - m2 = Melon.with_drb port: 8580 - - m2.add_remote port: 8489 - m1.add_remote port: 8580 - - m1.store ["hello", "world"] - m1.store ["hello", "world"] - msgs1 = m2.take_all [String, "world"] - msgs2 = m1.take_all [String, "world"], false - - assert_equal [["hello", "world"], ["hello", "world"]], msgs1 - assert msgs2.empty? - end - - def test_write_read - m1 = Melon.with_drb port: 8486 - m2 = Melon.with_drb port: 8587 - - m2.add_remote port: 8486 - m1.add_remote port: 8587 - - m1.write ["hello", "world"] - - msg1 = m2.read [String, "world"] - msg2 = m1.read [String, "world"] - - assert_equal ["hello", "world"], msg1 - assert_equal ["hello", "world"], msg2 - end - - def test_write_read_all - m1 = Melon.with_drb port: 8488 - m2 = Melon.with_drb port: 8589 - - m2.add_remote port: 8488 - m1.add_remote port: 8589 - - m1.write ["hello", "world"] - m2.write ["hello", "everyone"] - - msgs1 = m2.read_all ["hello", String] - msgs2 = m1.read_all ["hello", String] - - assert msgs1.include? ["hello", "world"] - assert msgs2.include? ["hello", "world"] - assert msgs1.include? ["hello", "everyone"] - assert msgs2.include? ["hello", "everyone"] + def make_melon port: nil + if port + Melon.with_drb port: port + else + Melon.with_drb + end end end diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000..cfa0218 --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,93 @@ +require 'minitest' +require 'minitest/autorun' +require 'minitest/pride' +require_relative '../lib/melon' + +module CoreMelonTests + @port = 8000 + + def self.port + @port += 1 + end + + def test_sanity + assert_kind_of Melon::Paradigm, make_melon + end + + def port1 + @port1 ||= CoreMelonTests.port + end + + def port2 + @port2 ||= CoreMelonTests.port + end + + def test_store_take + m1 = make_melon port: port1 + m2 = make_melon port: port2 + + m2.add_remote port: port1 + m1.add_remote port: port2 + + m1.store ["hello", "world"] + m1.store ["hello", "world"] + msg1 = m2.take [String, "world"] + msg2 = m2.take [String, "world"] + msg3 = m2.take [String, "world"], false + + assert_equal ["hello", "world"], msg1 + assert_equal ["hello", "world"], msg2 + assert_nil msg3 + end + + def test_store_take_all + m1 = make_melon port: port1 + m2 = make_melon port: port2 + + m2.add_remote port: port1 + m1.add_remote port: port2 + + m1.store ["hello", "world"] + m1.store ["hello", "world"] + msgs1 = m2.take_all [String, "world"] + msgs2 = m1.take_all [String, "world"], false + + assert_equal [["hello", "world"], ["hello", "world"]], msgs1 + assert msgs2.empty? + end + + def test_write_read + m1 = make_melon port: port1 + m2 = make_melon port: port2 + + m2.add_remote port: port1 + m1.add_remote port: port2 + + m1.write ["hello", "world"] + + msg1 = m2.read [String, "world"] + msg2 = m1.read [String, "world"] + + assert_equal ["hello", "world"], msg1 + assert_equal ["hello", "world"], msg2 + end + + def test_write_read_all + m1 = make_melon port: port1 + m2 = make_melon port: port2 + + m2.add_remote port: port1 + m1.add_remote port: port2 + + m1.write ["hello", "world"] + m2.write ["hello", "everyone"] + + msgs1 = m2.read_all ["hello", String] + msgs2 = m1.read_all ["hello", String] + + assert msgs1.include? ["hello", "world"] + assert msgs2.include? ["hello", "world"] + assert msgs1.include? ["hello", "everyone"] + assert msgs2.include? ["hello", "everyone"] + end +end diff --git a/test/test_local_storage.rb b/test/test_local_storage.rb index 3df428e..ffd07ef 100644 --- a/test/test_local_storage.rb +++ b/test/test_local_storage.rb @@ -1,3 +1,5 @@ +require_relative 'test_helper' + class TestLocalStorage < Minitest::Test def setup @ls = Melon::LocalStorage.new diff --git a/test/test_paradigm.rb b/test/test_paradigm.rb index 4790e0c..e194342 100644 --- a/test/test_paradigm.rb +++ b/test/test_paradigm.rb @@ -1,3 +1,4 @@ +require_relative 'test_helper' require_relative '../lib/melon/paradigm' class TestParadigm < Minitest::Test diff --git a/test/test_stored_message.rb b/test/test_stored_message.rb index 1d1ae5a..251ae6c 100644 --- a/test/test_stored_message.rb +++ b/test/test_stored_message.rb @@ -1,3 +1,5 @@ +require_relative 'test_helper' + class TestStoredMessage < Minitest::Test def test_creation m = ["hello", "world"] diff --git a/test/test_udp_melon.rb b/test/test_udp_melon.rb new file mode 100644 index 0000000..4fb4dba --- /dev/null +++ b/test/test_udp_melon.rb @@ -0,0 +1,18 @@ +require_relative 'test_helper' +require 'melon/udp' + +class TestUDPMelon < Minitest::Test + include CoreMelonTests + + def teardown + Melon.stop_udp + end + + def make_melon port: nil + if port + Melon.with_udp port: port + else + Melon.with_udp + end + end +end