funl 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 015bd0c0d3e5537bb8beeda5ccd1aa7502dd66c5
4
+ data.tar.gz: c5ce417f5a23cfce7a9d9aa920b6a65cc405dfff
5
+ SHA512:
6
+ metadata.gz: ccf71263bcda9155f8733c2a7f7adc0d23d526af9af16faa75ba0fed4fc4f59885444d2782550283aa52a7ec0e8c77435bb1aca9752d60ae349f00ebc73629dc
7
+ data.tar.gz: 10f240e8925c2aa63f21ac05f5ca12871d0df7f10d775e741019ecc1a889e927b57fdb183e211f4a4ce76b1c471883ec8b1f5222b57dd4b9ed899fe1b409268b
data/COPYING ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013, Joel VanderWerf, vjoel@users.sourceforge.net
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+ 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+
13
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,4 @@
1
+ funl
2
+ ====
3
+
4
+ Sequences messages.
@@ -0,0 +1,9 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+
4
+ desc "Run tests"
5
+ Rake::TestTask.new :test do |t|
6
+ t.libs << "lib"
7
+ t.libs << "ext"
8
+ t.test_files = FileList["test/**/*.rb"]
9
+ end
@@ -0,0 +1,31 @@
1
+ require 'object-stream'
2
+
3
+ module Funl
4
+ module Blobber
5
+ MARSHAL_TYPE = ObjectStream::MARSHAL_TYPE
6
+ YAML_TYPE = ObjectStream::YAML_TYPE
7
+ JSON_TYPE = ObjectStream::JSON_TYPE
8
+ MSGPACK_TYPE = ObjectStream::MSGPACK_TYPE
9
+
10
+ # Returns something which responds to #dump(obj) and #load(str).
11
+ def self.for type
12
+ case type
13
+ when MARSHAL_TYPE
14
+ Marshal
15
+ when YAML_TYPE
16
+ require 'yaml'
17
+ YAML
18
+ when JSON_TYPE
19
+ require 'yajl'
20
+ require 'yajl/json_gem'
21
+ ## would 'json' conflict with yajl required from other libs?
22
+ JSON
23
+ when MSGPACK_TYPE
24
+ require 'msgpack'
25
+ MessagePack
26
+ else
27
+ raise ArgumentError, "unknown type: #{type.inspect}"
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,67 @@
1
+ require 'logger'
2
+ require 'object-stream'
3
+ require 'funl/stream'
4
+
5
+ module Funl
6
+ # Assigns unique ids to clients.
7
+ class ClientSequencer
8
+ attr_reader :server
9
+ attr_reader :server_thread
10
+ attr_reader :next_id
11
+ attr_reader :log
12
+ attr_reader :stream_type
13
+
14
+ def initialize server, *conns, log: Logger.new($stderr),
15
+ stream_type: ObjectStream::MSGPACK_TYPE,
16
+ next_id: 0
17
+
18
+ @server = server
19
+ @log = log
20
+ @stream_type = stream_type
21
+ @next_id = next_id
22
+
23
+ conns.each do |conn|
24
+ handle_conn conn
25
+ end
26
+ end
27
+
28
+ def start
29
+ @server_thread = Thread.new do
30
+ run
31
+ end
32
+ end
33
+
34
+ def stop
35
+ server_thread.kill if server_thread
36
+ end
37
+
38
+ def wait
39
+ server_thread.join
40
+ end
41
+
42
+ def run
43
+ loop do
44
+ conn = server.accept
45
+ log.debug {"accepted #{conn.inspect}"}
46
+ handle_conn conn
47
+ end
48
+ rescue => ex
49
+ log.error ex
50
+ raise
51
+ end
52
+
53
+ def handle_conn conn
54
+ stream = ObjectStream.new(conn, type: stream_type)
55
+ msg = {"client_id" => next_id}
56
+ @next_id += 1
57
+ stream << msg
58
+ rescue IOError, SystemCallError => ex
59
+ log.error "write error for client #{next_id}: #{ex}"
60
+ else
61
+ log.info "recognized client #{next_id}"
62
+ ensure
63
+ stream.close if stream and not stream.closed?
64
+ end
65
+ private :handle_conn
66
+ end
67
+ end
@@ -0,0 +1,74 @@
1
+ require 'logger'
2
+ require 'funl/stream'
3
+ require 'funl/message'
4
+ require 'funl/blobber'
5
+
6
+ module Funl
7
+ # Generic client base class. Manages the setup and handshake on the streams
8
+ # to the client sequencer and the message sequencer.
9
+ class Client
10
+ include Funl::Stream
11
+
12
+ attr_reader :seq
13
+ attr_reader :cseq
14
+ attr_reader :arc
15
+ attr_reader :log
16
+ attr_reader :stream_type
17
+ attr_reader :message_class
18
+ attr_reader :client_id
19
+ attr_reader :greeting
20
+ attr_reader :start_tick
21
+ attr_reader :blob_type
22
+ attr_reader :blobber
23
+
24
+ def initialize(seq: seq!, cseq: cseq!, arc: nil,
25
+ log: Logger.new($stderr),
26
+ stream_type: ObjectStream::MSGPACK_TYPE,
27
+ message_class: Message)
28
+
29
+ @log = log
30
+ @stream_type = stream_type ## discover this thru connections
31
+ @message_class = message_class
32
+
33
+ @seq = client_stream_for(seq)
34
+ @cseq = client_stream_for(cseq)
35
+ @arcio = arc
36
+ end
37
+
38
+ # Handshake with both cseq and seq. Does not start any threads--that is left
39
+ # to subclasses. Yields after getting client id so that caller can set
40
+ # log.progname, for example.
41
+ def start
42
+ cseq_read_client_id
43
+ yield if block_given?
44
+ seq_read_greeting
45
+ end
46
+
47
+ def cseq_read_client_id
48
+ log.info "getting client_id from cseq"
49
+ @client_id = cseq.read["client_id"]
50
+ log.info "client_id = #{client_id}"
51
+ cseq.close rescue nil
52
+ @cseq = nil
53
+ end
54
+
55
+ def seq_read_greeting
56
+ log.info "getting greeting from seq"
57
+ @greeting = seq.read
58
+ @start_tick = greeting["tick"]
59
+ log.info "start_tick = #{start_tick}"
60
+ @blob_type = greeting["blob"]
61
+ log.info "blob_type = #{blob_type}"
62
+ @blobber = Blobber.for(blob_type)
63
+ seq.expect message_class
64
+
65
+ @arc = @arcio && client_stream_for(@arcio, type: blob_type)
66
+ # note: @arc is nil when client is the archiver itself
67
+ end
68
+
69
+ def arc_server_stream_for io
70
+ server_stream_for(io, type: blob_type)
71
+ # note: blob_type, not stream_type, since we are sending bare objects
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,12 @@
1
+ module Funl::HistoryClient
2
+ attr_reader :history_size
3
+
4
+ # The last N messages are kept.
5
+ HISTORY_SIZE = 1000
6
+
7
+ def initialize *args, history_size: HISTORY_SIZE, **opts
8
+ super *args, **opts
9
+ @history_size = history_size
10
+ end
11
+ end
12
+
@@ -0,0 +1,13 @@
1
+ module Funl::HistoryWorker
2
+ attr_reader :history
3
+
4
+ def initialize client
5
+ super
6
+ @history = client.history_size && Array.new(client.history_size)
7
+ # nil in case of use without UDP
8
+ end
9
+
10
+ def record_history msg
11
+ history[global_tick % history.size] = msg if history
12
+ end
13
+ end
@@ -0,0 +1,135 @@
1
+ require 'logger'
2
+ require 'funl/stream'
3
+ require 'funl/message'
4
+ require 'funl/blobber'
5
+
6
+ module Funl
7
+ # Assigns a unique sequential ids to each message and relays it to its
8
+ # destinations.
9
+ class MessageSequencer
10
+ include Funl::Stream
11
+
12
+ attr_reader :server
13
+ attr_reader :server_thread
14
+ attr_reader :streams
15
+ attr_reader :tick
16
+ attr_reader :log
17
+ attr_reader :stream_type
18
+ attr_reader :message_class
19
+ attr_reader :blob_type
20
+ attr_reader :greeting
21
+
22
+ def initialize server, *conns, log: Logger.new($stderr),
23
+ stream_type: ObjectStream::MSGPACK_TYPE,
24
+ message_class: Message,
25
+ blob_type: Blobber::MSGPACK_TYPE,
26
+ tick: 0
27
+
28
+ @server = server
29
+ @log = log
30
+ @stream_type = stream_type
31
+ @message_class = message_class
32
+ @blob_type = blob_type
33
+ @greeting = default_greeting
34
+ @tick = tick
35
+
36
+ @streams = []
37
+ conns.each do |conn|
38
+ try_conn conn
39
+ end
40
+ end
41
+
42
+ def default_greeting
43
+ {
44
+ "blob" => blob_type
45
+ }.freeze # can't change after initial conns read it
46
+ end
47
+
48
+ def try_conn conn
49
+ stream = message_server_stream_for(conn)
50
+ current_greeting = greeting.merge({"tick" => tick})
51
+ if write_succeeds?(current_greeting, stream)
52
+ log.info "connected #{stream.inspect}"
53
+ @streams << stream
54
+ end
55
+ end
56
+ private :try_conn
57
+
58
+ def start
59
+ @server_thread = Thread.new do
60
+ run
61
+ end
62
+ end
63
+
64
+ def stop
65
+ server_thread.kill if server_thread
66
+ end
67
+
68
+ def wait
69
+ server_thread.join
70
+ end
71
+
72
+ def run
73
+ loop do
74
+ readables, _ = select [server, *streams]
75
+
76
+ readables.each do |readable|
77
+ case readable
78
+ when server
79
+ begin
80
+ conn, addr = readable.accept_nonblock
81
+ log.info "accepted #{conn.inspect} from #{addr.inspect}"
82
+ try_conn conn
83
+ rescue IO::WaitReadable
84
+ next
85
+ end
86
+
87
+ else
88
+ log.debug {"readable = #{readable}"}
89
+ begin
90
+ msgs = []
91
+ readable.read do |msg|
92
+ msgs << msg
93
+ end
94
+ rescue IOError, SystemCallError => ex
95
+ log.debug {"closing #{readable}: #{ex}"}
96
+ @streams.delete readable
97
+ readable.close unless readable.closed?
98
+ else
99
+ log.debug {
100
+ "read #{msgs.size} messages from #{readable.peer_name}"}
101
+ end
102
+
103
+ msgs.each do |msg|
104
+ handle_message msg
105
+ end
106
+ end
107
+ end
108
+ end
109
+ rescue => ex
110
+ log.error ex
111
+ raise
112
+ end
113
+
114
+ def handle_message msg
115
+ log.debug {"handling message #{msg.inspect}"}
116
+ @tick += 1
117
+ msg.global_tick = tick
118
+ msg.delta = nil
119
+ @streams.keep_if do |stream|
120
+ write_succeeds? msg, stream
121
+ end
122
+ end
123
+ private :handle_message
124
+
125
+ def write_succeeds? data, stream
126
+ stream << data
127
+ true
128
+ rescue IOError, SystemCallError => ex
129
+ log.debug {"closing #{stream}: #{ex}"}
130
+ stream.close unless stream.closed?
131
+ false
132
+ end
133
+ private :write_succeeds?
134
+ end
135
+ end
@@ -0,0 +1,104 @@
1
+ module Funl
2
+ class Message
3
+ # Unique (per funl instance) sequential id of client who sent message.
4
+ attr_accessor :client_id
5
+
6
+ # Client's sequential id of message, unique only in client scope.
7
+ attr_accessor :local_tick
8
+
9
+ # Global sequential id of message, unique in scope of funl instance.
10
+ # In client request to funl, this means last ack-ed global tick.
11
+ # Assummed to be 64 bits, to avoid rollover errors.
12
+ attr_accessor :global_tick
13
+
14
+ # In client request, how far ahead of ack this message is. When
15
+ # messages are pipelined, delta > 1.
16
+ attr_accessor :delta
17
+
18
+ # Application-defined metadata. May be used for filtering etc.
19
+ attr_accessor :tags
20
+
21
+ # Application-defined payload data. See blobber.rb.
22
+ attr_accessor :blob
23
+
24
+ def initialize(*args)
25
+ @client_id, @local_tick, @global_tick, @delta, @tags, @blob = *args
26
+ end
27
+
28
+ def self.[](
29
+ client: nil, local: nil, global: nil, delta: nil, tags: nil, blob: nil)
30
+ new client, local, global, delta, tags, blob
31
+ end
32
+
33
+ def inspect
34
+ d = delta ? "+#{delta}" : nil
35
+ t = tags ? " #{tags}" : nil
36
+ s = [
37
+ "client #{client_id}",
38
+ "local #{local_tick}",
39
+ "global #{global_tick}#{d}"
40
+ ].join(", ")
41
+ "<Message: #{s}#{t}>"
42
+ end
43
+
44
+ def to_a
45
+ [@client_id, @local_tick, @global_tick, @delta, @tags, @blob]
46
+ end
47
+
48
+ def == other
49
+ other.kind_of? Message and
50
+ @client_id = other.client_id and
51
+ @local_tick = other.local_tick and
52
+ @global_tick = other.global_tick and
53
+ @delta = other.delta and
54
+ @tags = other.tags and
55
+ @blob = other.blob
56
+ end
57
+ alias eql? ==
58
+
59
+ def hash
60
+ @client_id.hash ^ @local_tick.hash ^ @global_tick.hash
61
+ end
62
+
63
+ # Call with Packer, nil, or IO. If +pk+ is nil, returns string. If +pk+ is
64
+ # a Packer, returns the Packer, which will need to be flushed. If +pk+ is
65
+ # IO, returns nil.
66
+ def to_msgpack(pk = nil)
67
+ case pk
68
+ when MessagePack::Packer
69
+ pk.write_array_header(6)
70
+ pk.write @client_id
71
+ pk.write @local_tick
72
+ pk.write @global_tick
73
+ pk.write @delta
74
+ pk.write @tags
75
+ pk.write @blob
76
+ return pk
77
+
78
+ else # nil or IO
79
+ MessagePack.pack(self, pk)
80
+ end
81
+ end
82
+
83
+ def to_json
84
+ to_a.to_json
85
+ end
86
+
87
+ def self.from_serialized ary
88
+ new *ary
89
+ end
90
+
91
+ def self.from_msgpack(src)
92
+ case src
93
+ when MessagePack::Unpacker
94
+ new(*src.read)
95
+
96
+ when IO, StringIO
97
+ from_msgpack(MessagePack::Unpacker.new(src))
98
+
99
+ else # String
100
+ from_msgpack(MessagePack::Unpacker.new.feed(src))
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,34 @@
1
+ require 'object-stream-wrapper'
2
+
3
+ module Funl
4
+ # Mixin depends on stream_type, log, client_id, message_class.
5
+ module Stream
6
+ def client_stream_for io, type: stream_type
7
+ ObjectStreamWrapper.new(io, type: type).tap do |stream|
8
+ stream.write_to_outbox {{"client_id" => client_id}}
9
+ # client_id will be nil in the case of cseq, but that's ok.
10
+ end
11
+ end
12
+
13
+ def server_stream_for io, type: stream_type
14
+ ObjectStreamWrapper.new(io, type: type).tap do |stream|
15
+ stream.consume do |h|
16
+ client_id = h["client_id"]
17
+ stream.peer_name = "client #{client_id}"
18
+ log.info "peer is #{stream.peer_name}"
19
+ end
20
+ end
21
+ end
22
+
23
+ def message_server_stream_for io, type: stream_type
24
+ ObjectStreamWrapper.new(io, type: type).tap do |stream|
25
+ stream.consume do |h|
26
+ client_id = h["client_id"]
27
+ stream.peer_name = "client #{client_id}"
28
+ log.info "peer is #{stream.peer_name}"
29
+ stream.expect message_class
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,69 @@
1
+ require 'funl/client-sequencer'
2
+ require 'socket'
3
+ require 'tmpdir'
4
+
5
+ require 'minitest/autorun'
6
+
7
+ class TestClientSequencer < Minitest::Test
8
+ attr_reader :log
9
+
10
+ def setup
11
+ @dir = Dir.mktmpdir "funl-test-cseq-"
12
+ @path = File.join(@dir, "sock")
13
+ @log = Logger.new($stderr)
14
+ log.level = Logger::WARN
15
+ end
16
+
17
+ def teardown
18
+ FileUtils.remove_entry @dir
19
+ end
20
+
21
+ def test_initial_conns
22
+ as = []; bs = []
23
+ 3.times {a, b = UNIXSocket.pair; as << a; bs << b}
24
+ cseq = Funl::ClientSequencer.new nil, *as, log: log
25
+ bs.each_with_index do |b, i|
26
+ stream = ObjectStream.new(b, type: cseq.stream_type)
27
+ client_id = stream.read["client_id"]
28
+ assert_equal i, client_id
29
+ end
30
+ end
31
+
32
+ def test_later_conns
33
+ svr = UNIXServer.new(@path)
34
+ cseq = Funl::ClientSequencer.new svr, log: log
35
+ cseq.start
36
+ 3.times do |i|
37
+ conn = UNIXSocket.new(@path)
38
+ stream = ObjectStream.new(conn, type: cseq.stream_type)
39
+ client_id = stream.read["client_id"]
40
+ assert_equal i, client_id
41
+ end
42
+ ensure
43
+ cseq.stop rescue nil
44
+ end
45
+
46
+ def test_persist
47
+ saved_next_id = 0
48
+ 3.times do |i|
49
+ assert_equal i, saved_next_id
50
+
51
+ path = "#{@path}-#{i}"
52
+ svr = UNIXServer.new(path)
53
+ cseq = Funl::ClientSequencer.new svr, log: log,
54
+ next_id: saved_next_id
55
+ cseq.start
56
+
57
+ conn = UNIXSocket.new(path)
58
+ stream = ObjectStream.new(conn, type: cseq.stream_type)
59
+ client_id = stream.read["client_id"]
60
+ assert_equal i, client_id
61
+
62
+ cseq.stop
63
+ cseq.wait
64
+ saved_next_id = cseq.next_id
65
+ end
66
+ ensure
67
+ cseq.stop rescue nil
68
+ end
69
+ end
@@ -0,0 +1,50 @@
1
+ require 'funl/client'
2
+ require 'funl/blobber'
3
+ require 'funl/message-sequencer'
4
+ require 'funl/client-sequencer'
5
+ require 'socket'
6
+ require 'tmpdir'
7
+
8
+ include Funl
9
+
10
+ require 'minitest/autorun'
11
+
12
+ class TestClient < Minitest::Test
13
+ attr_reader :log
14
+
15
+ def setup
16
+ @dir = Dir.mktmpdir "funl-test-client-"
17
+ @cseq_path = File.join(@dir, "cseq")
18
+ @seq_path = File.join(@dir, "seq")
19
+ @log = Logger.new($stderr)
20
+ log.level = Logger::WARN
21
+ end
22
+
23
+ def teardown
24
+ FileUtils.remove_entry @dir
25
+ end
26
+
27
+ def test_client
28
+ cseq_sock = UNIXServer.new(@cseq_path)
29
+ cseq = ClientSequencer.new cseq_sock, log: log
30
+ cseq.start
31
+
32
+ seq_sock = UNIXServer.new(@seq_path)
33
+ seq = MessageSequencer.new seq_sock, log: log
34
+ seq.start
35
+
36
+ client = Client.new(
37
+ seq: UNIXSocket.new(@seq_path),
38
+ cseq: UNIXSocket.new(@cseq_path),
39
+ log: log)
40
+
41
+ client.start
42
+
43
+ assert_equal(0, client.client_id)
44
+ assert_equal(0, client.start_tick)
45
+ assert_equal(Funl::Blobber::MSGPACK_TYPE, client.blob_type)
46
+ ensure
47
+ cseq.stop rescue nil
48
+ seq.stop rescue nil
49
+ end
50
+ end
@@ -0,0 +1,122 @@
1
+ require 'funl/message-sequencer'
2
+ require 'socket'
3
+ require 'tmpdir'
4
+
5
+ include Funl
6
+
7
+ require 'minitest/autorun'
8
+
9
+ class TestMessageSequencer < Minitest::Test
10
+ attr_reader :log
11
+
12
+ def setup
13
+ @dir = Dir.mktmpdir "funl-test-mseq-"
14
+ @path = File.join(@dir, "sock")
15
+ @log = Logger.new($stderr)
16
+ log.level = Logger::WARN
17
+ @n_clients = 3
18
+ end
19
+
20
+ def teardown
21
+ FileUtils.remove_entry @dir
22
+ end
23
+
24
+ def test_initial_conns
25
+ as = []; bs = []
26
+ @n_clients.times {a, b = UNIXSocket.pair; as << a; bs << b}
27
+ mseq = MessageSequencer.new nil, *as, log: log
28
+ bs.each_with_index do |b, i|
29
+ stream = ObjectStreamWrapper.new(b, type: mseq.stream_type)
30
+ stream.write_to_outbox({"client_id" => "test_initial_conns #{i}"})
31
+ global_tick = stream.read["tick"]
32
+ assert_equal 0, global_tick
33
+ end
34
+ end
35
+
36
+ def test_later_conns
37
+ stream_type = ObjectStream::MSGPACK_TYPE
38
+ svr = UNIXServer.new(@path)
39
+ pid = fork do
40
+ log.progname = "mseq"
41
+ mseq = MessageSequencer.new svr, log: log, stream_type: stream_type
42
+ mseq.start
43
+ sleep
44
+ end
45
+
46
+ log.progname = "client"
47
+ streams = (0...@n_clients).map do
48
+ conn = UNIXSocket.new(@path)
49
+ stream = ObjectStreamWrapper.new(conn, type: stream_type)
50
+ stream.write_to_outbox{{"client_id" => "test_later_conns"}}
51
+ global_tick = stream.read["tick"]
52
+ assert_equal 0, global_tick
53
+ stream
54
+ end
55
+
56
+ m1 = Message[
57
+ client: 0, local: 12, global: 34,
58
+ delta: 1, tags: ["foo"], blob: "BLOB"]
59
+ send_msg(src: streams[0], message: m1, dst: streams, expected_tick: 1)
60
+
61
+ if @n_clients > 1
62
+ m2 = Message[
63
+ client: 1, local: 23, global: 45,
64
+ delta: 4, tags: ["bar"], blob: "BLOB"]
65
+ send_msg(src: streams[1], message: m2, dst: streams, expected_tick: 2)
66
+ end
67
+
68
+ ensure
69
+ Process.kill "TERM", pid if pid
70
+ end
71
+
72
+ def send_msg(src: nil, message: nil, dst: nil, expected_tick: nil)
73
+ src << message
74
+
75
+ replies = dst.map do |stream|
76
+ stream.expect Message
77
+ stream.read
78
+ end
79
+
80
+ assert_equal @n_clients, replies.size
81
+
82
+ replies.each do |r|
83
+ assert_equal(message.client_id, r.client_id)
84
+ assert_equal(message.local_tick, r.local_tick)
85
+ assert_equal(expected_tick, r.global_tick)
86
+ assert_equal(nil, r.delta)
87
+ assert_equal(message.tags, r.tags)
88
+ assert_equal(message.blob, r.blob)
89
+ end
90
+ end
91
+
92
+ def test_persist
93
+ saved_tick = 0
94
+ n_write = 0
95
+ 3.times do |i|
96
+ assert_equal n_write, saved_tick
97
+
98
+ path = "#{@path}-#{i}"
99
+ svr = UNIXServer.new(path)
100
+ mseq = Funl::MessageSequencer.new svr, log: log,
101
+ tick: saved_tick
102
+ mseq.start
103
+
104
+ conn = UNIXSocket.new(path)
105
+ stream = ObjectStreamWrapper.new(conn, type: mseq.stream_type)
106
+ stream.write_to_outbox{{"client_id" => "test_persist"}} # not needed
107
+ tick = stream.read["tick"]
108
+ assert_equal n_write, tick
109
+
110
+ stream.write Message.new
111
+ stream.read
112
+ n_write += 1
113
+
114
+ mseq.stop
115
+ mseq.wait
116
+ saved_tick = mseq.tick
117
+ end
118
+
119
+ ensure
120
+ mseq.stop rescue nil
121
+ end
122
+ end
@@ -0,0 +1,75 @@
1
+ require 'funl/message'
2
+ require 'socket'
3
+ require 'stringio'
4
+ require 'msgpack'
5
+
6
+ require 'minitest/autorun'
7
+
8
+ class TestMessage < Minitest::Test
9
+ attr_reader :m, :sio
10
+
11
+ def setup
12
+ @m = Funl::Message.new
13
+ m.client_id = 12
14
+ m.local_tick = 34
15
+ m.global_tick = 56
16
+ m.delta = 78
17
+ m.tags = [ "aaa", "bbb" ]
18
+ m.blob = "BLOB"
19
+ end
20
+
21
+ def test_from_serialized
22
+ a = m.to_a
23
+ m2 = Funl::Message.from_serialized a
24
+ assert_equal(m, m2)
25
+ end
26
+
27
+ def test_to_msgpack
28
+ assert_kind_of(String, m.to_msgpack(nil))
29
+ assert_kind_of(String, m.to_msgpack)
30
+ assert_equal(MessagePack.pack(m), m.to_msgpack(nil))
31
+ assert_equal(MessagePack.pack(m), m.to_msgpack)
32
+ end
33
+
34
+ def test_to_msgpack_packer
35
+ pk = MessagePack::Packer.new
36
+ assert_kind_of(MessagePack::Packer, m.to_msgpack(pk))
37
+ assert_equal(pk, m.to_msgpack(pk))
38
+ end
39
+
40
+ def test_to_msgpack_io
41
+ src, dst = UNIXSocket.pair
42
+ assert_nil(m.to_msgpack(src))
43
+ src.close
44
+ assert_equal(MessagePack.pack(m), dst.read)
45
+ end
46
+
47
+ def test_pack_io
48
+ src, dst = UNIXSocket.pair
49
+ MessagePack.pack(m, src)
50
+ src.close
51
+ assert_equal(MessagePack.pack(m), dst.read)
52
+ end
53
+
54
+ def test_pack_unpack
55
+ assert_equal(m.to_a, MessagePack.unpack(MessagePack.pack(m)))
56
+ end
57
+
58
+ def test_from_msgpack_str
59
+ assert_equal(m, Funl::Message.from_msgpack(MessagePack.pack(m)))
60
+ end
61
+
62
+ def test_from_msgpack_stringio
63
+ sio = StringIO.new
64
+ MessagePack.pack(m, sio)
65
+ sio.rewind
66
+ assert_equal(m, Funl::Message.from_msgpack(sio))
67
+ end
68
+
69
+ def test_from_msgpack_io
70
+ src, dst = UNIXSocket.pair
71
+ MessagePack.pack(m, src)
72
+ src.close
73
+ assert_equal(m, Funl::Message.from_msgpack(dst))
74
+ end
75
+ end
@@ -0,0 +1,53 @@
1
+ require 'funl/stream'
2
+ require 'funl/message'
3
+ require 'stringio'
4
+ require 'logger'
5
+
6
+ require 'minitest/autorun'
7
+
8
+ class TestStream < Minitest::Test
9
+ include Funl::Stream
10
+
11
+ attr_reader :client_id
12
+ attr_reader :stream_type
13
+ attr_reader :message_class
14
+ attr_reader :log
15
+
16
+ def setup
17
+ # for the Stream mixin:
18
+ @client_id = 42
19
+ @stream_type = ObjectStream::MSGPACK_TYPE
20
+ @message_class = Funl::Message
21
+ @log = Logger.new($stderr)
22
+ log.level = Logger::WARN
23
+
24
+ @sio = StringIO.new
25
+ end
26
+
27
+ def test_greeting
28
+ client = client_stream_for @sio
29
+ client.write "message"
30
+
31
+ @sio.rewind
32
+
33
+ server = server_stream_for @sio
34
+ m = server.read
35
+
36
+ assert_equal "message", m
37
+ assert_equal "client #{client_id}", server.peer_name
38
+ end
39
+
40
+ def test_message_server_stream
41
+ client = client_stream_for @sio
42
+ client.write Funl::Message[client: client_id]
43
+
44
+ @sio.rewind
45
+
46
+ server = message_server_stream_for @sio
47
+ m = server.read
48
+
49
+ assert_kind_of Funl::Message, m
50
+ assert_equal client_id, m.client_id
51
+ assert_equal "client #{client_id}", server.peer_name
52
+ end
53
+ end
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: funl
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Joel VanderWerf
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-07-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: object-stream
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Sequences messages.
28
+ email: vjoel@users.sourceforge.net
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files:
32
+ - README.md
33
+ - COPYING
34
+ files:
35
+ - README.md
36
+ - COPYING
37
+ - Rakefile
38
+ - lib/funl/stream.rb
39
+ - lib/funl/blobber.rb
40
+ - lib/funl/history-client.rb
41
+ - lib/funl/history-worker.rb
42
+ - lib/funl/client.rb
43
+ - lib/funl/message-sequencer.rb
44
+ - lib/funl/client-sequencer.rb
45
+ - lib/funl/message.rb
46
+ - test/test-message-sequencer.rb
47
+ - test/test-client-sequencer.rb
48
+ - test/test-message.rb
49
+ - test/test-stream.rb
50
+ - test/test-client.rb
51
+ homepage: https://github.com/vjoel/funl
52
+ licenses:
53
+ - BSD
54
+ metadata: {}
55
+ post_install_message:
56
+ rdoc_options:
57
+ - --quiet
58
+ - --line-numbers
59
+ - --inline-source
60
+ - --title
61
+ - funl
62
+ - --main
63
+ - README.md
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirements: []
77
+ rubyforge_project:
78
+ rubygems_version: 2.0.4
79
+ signing_key:
80
+ specification_version: 4
81
+ summary: Sequences messages
82
+ test_files:
83
+ - test/test-message-sequencer.rb
84
+ - test/test-client-sequencer.rb
85
+ - test/test-message.rb
86
+ - test/test-stream.rb
87
+ - test/test-client.rb
88
+ has_rdoc: