funl 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: