object-stream 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.
- checksums.yaml +7 -0
- data/COPYING +22 -0
- data/README.md +7 -0
- data/Rakefile +9 -0
- data/bench/pipe-socket.rb +52 -0
- data/examples/basic-usage.rb +43 -0
- data/examples/custom-class.rb +73 -0
- data/examples/maxbuf.rb +17 -0
- data/examples/outbox.rb +19 -0
- data/examples/pipe.rb +42 -0
- data/examples/read-without-block.rb +25 -0
- data/examples/slow-sender.rb +76 -0
- data/examples/socket.rb +43 -0
- data/examples/udp.rb +35 -0
- data/lib/object-stream-wrapper.rb +114 -0
- data/lib/object-stream.rb +324 -0
- data/test/test-basic.rb +176 -0
- data/test/test-consume.rb +51 -0
- data/test/test-expect.rb +80 -0
- data/test/test-inbox.rb +65 -0
- data/test/test-maxbuf.rb +22 -0
- data/test/test-outbox.rb +35 -0
- data/test/test-slow-sender.rb +79 -0
- metadata +110 -0
data/test/test-basic.rb
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'object-stream'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
require 'minitest/autorun'
|
5
|
+
|
6
|
+
module TestBasic
|
7
|
+
attr_reader :sio, :stream
|
8
|
+
|
9
|
+
# supported by all types
|
10
|
+
BASIC_OBJECTS = [
|
11
|
+
nil,
|
12
|
+
true,
|
13
|
+
false,
|
14
|
+
"The quick brown fox jumped over the lazy dog's back.",
|
15
|
+
[-5, "foo", [4]],
|
16
|
+
{"a" => 1, "b" => 2}
|
17
|
+
]
|
18
|
+
|
19
|
+
ADVANCED_OBJECTS = [
|
20
|
+
12,
|
21
|
+
2**40 + 123,
|
22
|
+
3.45,
|
23
|
+
{ 1 => 2 },
|
24
|
+
{ ["a"] => 3 },
|
25
|
+
{ {"b" => 5} => 6 }
|
26
|
+
]
|
27
|
+
|
28
|
+
class Custom
|
29
|
+
attr_reader :x, :y
|
30
|
+
def initialize x, y
|
31
|
+
@x, @y = x, y
|
32
|
+
end
|
33
|
+
def ==(other)
|
34
|
+
@x == other.x
|
35
|
+
@y == other.y # just enough to make test pass
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
RUBY_OBJECTS = [
|
40
|
+
:foo,
|
41
|
+
{:foo => :bar},
|
42
|
+
String,
|
43
|
+
File,
|
44
|
+
Custom.new(1,2)
|
45
|
+
]
|
46
|
+
|
47
|
+
def type; self.class::TYPE; end
|
48
|
+
def objects; BASIC_OBJECTS + self.class::OBJECTS; end
|
49
|
+
|
50
|
+
def setup
|
51
|
+
@sio = StringIO.new
|
52
|
+
@stream = ObjectStream.new sio, type: type
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_write_read
|
56
|
+
objects.each do |obj|
|
57
|
+
sio.rewind # do not need to clear stream's buffer (if any)
|
58
|
+
sio.truncate 0
|
59
|
+
stream.write obj
|
60
|
+
|
61
|
+
sio.rewind
|
62
|
+
dump = sio.read
|
63
|
+
sio.rewind
|
64
|
+
|
65
|
+
stream.read do |obj2|
|
66
|
+
assert_equal(obj, obj2, "dump is #{dump.inspect}")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_batch_write
|
72
|
+
a = ["a", "b", "c"]
|
73
|
+
stream.write *a
|
74
|
+
sio.rewind
|
75
|
+
dump = sio.read
|
76
|
+
sio.rewind
|
77
|
+
assert_equal(a, stream.to_a, "dump is #{dump.inspect}")
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_each
|
81
|
+
objects.each do |obj|
|
82
|
+
stream.write obj
|
83
|
+
end
|
84
|
+
|
85
|
+
sio.rewind
|
86
|
+
dump = sio.read
|
87
|
+
sio.rewind
|
88
|
+
|
89
|
+
assert_equal(objects, stream.to_a, # <-- #each called by #to_a
|
90
|
+
"dump is #{dump.inspect}")
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_break
|
94
|
+
a = (1..10).to_a
|
95
|
+
a.each do |i|
|
96
|
+
stream.write [i]
|
97
|
+
end
|
98
|
+
|
99
|
+
sio.rewind
|
100
|
+
|
101
|
+
a2 = []
|
102
|
+
stream.each do |object|
|
103
|
+
i = object[0]
|
104
|
+
a2 << i
|
105
|
+
break if i == 5
|
106
|
+
end
|
107
|
+
|
108
|
+
stream.each do |object|
|
109
|
+
i = object[0]
|
110
|
+
a2 << i
|
111
|
+
end
|
112
|
+
|
113
|
+
case type
|
114
|
+
when ObjectStream::MARSHAL_TYPE
|
115
|
+
assert_equal(a, a2)
|
116
|
+
else
|
117
|
+
assert_equal(a[0..4], a2) # fixable?
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_enum
|
122
|
+
objects.each do |obj|
|
123
|
+
stream.write obj
|
124
|
+
end
|
125
|
+
|
126
|
+
sio.rewind
|
127
|
+
|
128
|
+
enum = stream.each
|
129
|
+
assert_equal(objects, enum.to_a)
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_read_without_block
|
133
|
+
n = 100
|
134
|
+
n.times do |i|
|
135
|
+
stream << [i]
|
136
|
+
end
|
137
|
+
|
138
|
+
stream.io.rewind
|
139
|
+
|
140
|
+
count = 0
|
141
|
+
until stream.eof?
|
142
|
+
obj = stream.read
|
143
|
+
assert_equal [count], obj
|
144
|
+
count += 1
|
145
|
+
end
|
146
|
+
assert_equal n, count
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
class TestBasicMarshal < Minitest::Test
|
151
|
+
include TestBasic
|
152
|
+
|
153
|
+
TYPE = ObjectStream::MARSHAL_TYPE
|
154
|
+
OBJECTS = ADVANCED_OBJECTS + RUBY_OBJECTS
|
155
|
+
end
|
156
|
+
|
157
|
+
class TestBasicYaml < Minitest::Test
|
158
|
+
include TestBasic
|
159
|
+
|
160
|
+
TYPE = ObjectStream::YAML_TYPE
|
161
|
+
OBJECTS = ADVANCED_OBJECTS + RUBY_OBJECTS
|
162
|
+
end
|
163
|
+
|
164
|
+
class TestBasicJson < Minitest::Test
|
165
|
+
include TestBasic
|
166
|
+
|
167
|
+
TYPE = ObjectStream::JSON_TYPE
|
168
|
+
OBJECTS = [] # poor json!
|
169
|
+
end
|
170
|
+
|
171
|
+
class TestBasicMsgpack < Minitest::Test
|
172
|
+
include TestBasic
|
173
|
+
|
174
|
+
TYPE = ObjectStream::MSGPACK_TYPE
|
175
|
+
OBJECTS = ADVANCED_OBJECTS
|
176
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'object-stream-wrapper'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
require 'minitest/autorun'
|
5
|
+
|
6
|
+
class TestConsume < Minitest::Test
|
7
|
+
attr_reader :sio
|
8
|
+
|
9
|
+
def setup
|
10
|
+
@sio = StringIO.new
|
11
|
+
end
|
12
|
+
|
13
|
+
ObjectStream::TYPES.each do |type|
|
14
|
+
define_method "test_consume_#{type}" do
|
15
|
+
do_test_consume_for type: type
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def do_test_consume_for(type: type)
|
20
|
+
n_total = 10
|
21
|
+
n_consumed = 5
|
22
|
+
|
23
|
+
objects = (0...n_total).map {|i| [i]}
|
24
|
+
|
25
|
+
stream = ObjectStreamWrapper.new(sio, type: type)
|
26
|
+
objects.each do |object|
|
27
|
+
stream << object
|
28
|
+
end
|
29
|
+
|
30
|
+
sio.rewind
|
31
|
+
stream = ObjectStreamWrapper.new(sio, type: type)
|
32
|
+
|
33
|
+
count = 0
|
34
|
+
|
35
|
+
n_consumed.times do |i|
|
36
|
+
stream.consume do |a|
|
37
|
+
assert_equal(i, a[0])
|
38
|
+
count += 1
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
assert_equal(0, sio.pos)
|
43
|
+
|
44
|
+
stream.each_with_index do |a, i|
|
45
|
+
assert_equal i + n_consumed, a[0]
|
46
|
+
count += 1
|
47
|
+
end
|
48
|
+
|
49
|
+
assert_equal(n_total, count)
|
50
|
+
end
|
51
|
+
end
|
data/test/test-expect.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'object-stream-wrapper'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
require 'minitest/autorun'
|
5
|
+
|
6
|
+
class TestExpect < Minitest::Test
|
7
|
+
attr_reader :sio
|
8
|
+
|
9
|
+
class A
|
10
|
+
def initialize x, y
|
11
|
+
@x, @y = x, y
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_msgpack pk = nil
|
15
|
+
case pk
|
16
|
+
when MessagePack::Packer
|
17
|
+
pk.write_array_header(2)
|
18
|
+
pk.write @x
|
19
|
+
pk.write @y
|
20
|
+
return pk
|
21
|
+
|
22
|
+
else # nil or IO
|
23
|
+
MessagePack.pack(self, pk)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_a
|
28
|
+
[@x, @y]
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_json
|
32
|
+
to_a.to_json
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.from_serialized ary
|
36
|
+
new *ary
|
37
|
+
end
|
38
|
+
|
39
|
+
def == other
|
40
|
+
self.class == other.class and
|
41
|
+
to_a == other.to_a
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class B < A
|
46
|
+
end
|
47
|
+
|
48
|
+
def setup
|
49
|
+
@sio = StringIO.new
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_expect
|
53
|
+
objects = []
|
54
|
+
20.times do |i|
|
55
|
+
if rand < 0.5
|
56
|
+
objects << "A" << A.new(i, i.to_s)
|
57
|
+
else
|
58
|
+
objects << "B" << B.new(i, i.to_s)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
stream = ObjectStreamWrapper.new(sio, type: ObjectStream::MSGPACK_TYPE)
|
63
|
+
objects.each do |object|
|
64
|
+
stream << object
|
65
|
+
end
|
66
|
+
|
67
|
+
sio.rewind
|
68
|
+
stream = ObjectStreamWrapper.new(sio, type: ObjectStream::MSGPACK_TYPE)
|
69
|
+
objects2 = []
|
70
|
+
stream.read do |object|
|
71
|
+
case object
|
72
|
+
when "A"; stream.expect A
|
73
|
+
when "B"; stream.expect B
|
74
|
+
else stream.unexpect
|
75
|
+
end
|
76
|
+
objects2 << object
|
77
|
+
end
|
78
|
+
assert_equal objects, objects2
|
79
|
+
end
|
80
|
+
end
|
data/test/test-inbox.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'object-stream'
|
2
|
+
require 'socket'
|
3
|
+
|
4
|
+
require 'minitest/autorun'
|
5
|
+
|
6
|
+
class TestInbox < Minitest::Test
|
7
|
+
attr_reader :s, :t
|
8
|
+
|
9
|
+
def setup
|
10
|
+
@s, @t = UNIXSocket.pair
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_marshal
|
14
|
+
do_test(ObjectStream::MARSHAL_TYPE)
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_yaml
|
18
|
+
do_test(ObjectStream::YAML_TYPE)
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_json
|
22
|
+
do_test(ObjectStream::JSON_TYPE)
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_msgpack
|
26
|
+
do_test(ObjectStream::MSGPACK_TYPE)
|
27
|
+
end
|
28
|
+
|
29
|
+
def do_test type
|
30
|
+
n = 200
|
31
|
+
th = Thread.new do
|
32
|
+
src = ObjectStream.new(s, type: type)
|
33
|
+
n.times do |i|
|
34
|
+
src << [i]
|
35
|
+
end
|
36
|
+
src.close
|
37
|
+
end
|
38
|
+
|
39
|
+
dst = ObjectStream.new(t, type: type)
|
40
|
+
i = 0
|
41
|
+
|
42
|
+
begin
|
43
|
+
rand(5).times do
|
44
|
+
assert_equal(i, dst.read[0])
|
45
|
+
i+=1
|
46
|
+
end
|
47
|
+
rescue EOFError
|
48
|
+
end
|
49
|
+
|
50
|
+
begin
|
51
|
+
dst.read do |obj|
|
52
|
+
assert_equal(i, obj[0])
|
53
|
+
i+=1
|
54
|
+
end
|
55
|
+
rescue EOFError
|
56
|
+
end
|
57
|
+
|
58
|
+
dst.each do |obj|
|
59
|
+
assert_equal(i, obj[0])
|
60
|
+
i+=1
|
61
|
+
end
|
62
|
+
|
63
|
+
assert_equal n, i
|
64
|
+
end
|
65
|
+
end
|
data/test/test-maxbuf.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'object-stream'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
require 'minitest/autorun'
|
5
|
+
|
6
|
+
class TestMaxbuf < Minitest::Test
|
7
|
+
attr_reader :sio, :stream
|
8
|
+
|
9
|
+
def setup
|
10
|
+
@sio = StringIO.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_maxbuf
|
14
|
+
stream = ObjectStream.new(sio, type: ObjectStream::MSGPACK_TYPE)
|
15
|
+
stream << "a"*20
|
16
|
+
sio.rewind
|
17
|
+
stream = ObjectStream.new(sio, type: ObjectStream::MSGPACK_TYPE, maxbuf: 20)
|
18
|
+
assert_raises(ObjectStream::OverflowError) do
|
19
|
+
stream.to_a
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/test/test-outbox.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'object-stream'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
require 'minitest/autorun'
|
5
|
+
|
6
|
+
class TestOutbox < Minitest::Test
|
7
|
+
attr_reader :sio
|
8
|
+
|
9
|
+
def setup
|
10
|
+
@sio = StringIO.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_outbox_is_lazy
|
14
|
+
stream = ObjectStream.new(sio, type: ObjectStream::MSGPACK_TYPE)
|
15
|
+
stream.write_to_outbox "foo"
|
16
|
+
sio.rewind
|
17
|
+
assert_empty sio.read
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_outbox_precedes
|
21
|
+
stream = ObjectStream.new(sio, type: ObjectStream::MSGPACK_TYPE)
|
22
|
+
n_foo = stream.max_outbox+10
|
23
|
+
|
24
|
+
n_foo.times do |i|
|
25
|
+
stream.write_to_outbox "foo#{i}"
|
26
|
+
end
|
27
|
+
stream.write "bar"
|
28
|
+
sio.rewind
|
29
|
+
|
30
|
+
stream = ObjectStream.new(sio, type: ObjectStream::MSGPACK_TYPE)
|
31
|
+
items = stream.to_a
|
32
|
+
assert_equal n_foo + 1, items.size
|
33
|
+
assert_equal "bar", items.last
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'object-stream'
|
2
|
+
require 'socket'
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
require 'minitest/autorun'
|
6
|
+
|
7
|
+
class TestSlowSender < Minitest::Test
|
8
|
+
attr_reader :s, :t
|
9
|
+
|
10
|
+
def setup
|
11
|
+
@s, @t = UNIXSocket.pair
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_marshal
|
15
|
+
assert_equal(:block, get_test_result(ObjectStream::MARSHAL_TYPE))
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_yaml
|
19
|
+
assert_equal(:block, get_test_result(ObjectStream::YAML_TYPE))
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_json
|
23
|
+
assert_equal(:noblock, get_test_result(ObjectStream::JSON_TYPE))
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_msgpack
|
27
|
+
assert_equal(:noblock, get_test_result(ObjectStream::MSGPACK_TYPE))
|
28
|
+
end
|
29
|
+
|
30
|
+
def get_test_result type
|
31
|
+
pid = fork do
|
32
|
+
sio = StringIO.new
|
33
|
+
stream = ObjectStream.new(sio, type: type)
|
34
|
+
10.times do |i|
|
35
|
+
stream << "foo bar #{i}"
|
36
|
+
end
|
37
|
+
|
38
|
+
sio.rewind
|
39
|
+
data = sio.read
|
40
|
+
pos = data.index "bar 5"
|
41
|
+
raise unless pos < data.size - 10 # assume strings not munged
|
42
|
+
s.write data[0...pos]
|
43
|
+
sleep 0.1
|
44
|
+
s.write data[pos...pos+1]
|
45
|
+
sleep 0.1
|
46
|
+
s.write data[pos+1...pos+2]
|
47
|
+
sleep 0.1
|
48
|
+
s.write data[pos+2..-1]
|
49
|
+
end
|
50
|
+
|
51
|
+
s.close
|
52
|
+
stream = ObjectStream.new(t, type: type)
|
53
|
+
|
54
|
+
select_count = 0
|
55
|
+
empty_read_count = 0
|
56
|
+
until stream.eof?
|
57
|
+
select_count += 1
|
58
|
+
select([stream])
|
59
|
+
|
60
|
+
obj_count = 0
|
61
|
+
stream.read do |obj|
|
62
|
+
obj_count += 1
|
63
|
+
end
|
64
|
+
if obj_count == 0
|
65
|
+
empty_read_count += 1
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
stream.close
|
70
|
+
if select_count > 1 and empty_read_count > 0
|
71
|
+
:noblock
|
72
|
+
else
|
73
|
+
:block
|
74
|
+
end
|
75
|
+
|
76
|
+
ensure
|
77
|
+
Process.wait pid if pid
|
78
|
+
end
|
79
|
+
end
|