msgpack 1.4.2 → 1.6.0
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 +4 -4
- data/.github/workflows/ci.yaml +57 -0
- data/ChangeLog +60 -0
- data/README.md +25 -1
- data/Rakefile +1 -2
- data/bench/bench.rb +78 -0
- data/bin/console +8 -0
- data/doclib/msgpack/factory.rb +47 -3
- data/doclib/msgpack/packer.rb +5 -4
- data/doclib/msgpack/unpacker.rb +2 -2
- data/ext/java/org/msgpack/jruby/Buffer.java +23 -16
- data/ext/java/org/msgpack/jruby/Decoder.java +29 -21
- data/ext/java/org/msgpack/jruby/Encoder.java +68 -30
- data/ext/java/org/msgpack/jruby/ExtensionRegistry.java +37 -49
- data/ext/java/org/msgpack/jruby/ExtensionValue.java +5 -8
- data/ext/java/org/msgpack/jruby/Factory.java +47 -7
- data/ext/java/org/msgpack/jruby/Packer.java +29 -17
- data/ext/java/org/msgpack/jruby/Unpacker.java +66 -42
- data/ext/msgpack/buffer.c +38 -57
- data/ext/msgpack/buffer.h +19 -10
- data/ext/msgpack/buffer_class.c +90 -52
- data/ext/msgpack/compat.h +0 -99
- data/ext/msgpack/extconf.rb +9 -22
- data/ext/msgpack/factory_class.c +133 -43
- data/ext/msgpack/packer.c +60 -36
- data/ext/msgpack/packer.h +27 -18
- data/ext/msgpack/packer_class.c +84 -77
- data/ext/msgpack/packer_class.h +11 -0
- data/ext/msgpack/packer_ext_registry.c +24 -32
- data/ext/msgpack/packer_ext_registry.h +40 -33
- data/ext/msgpack/sysdep.h +5 -2
- data/ext/msgpack/unpacker.c +128 -97
- data/ext/msgpack/unpacker.h +17 -10
- data/ext/msgpack/unpacker_class.c +75 -80
- data/ext/msgpack/unpacker_class.h +11 -0
- data/ext/msgpack/unpacker_ext_registry.c +42 -18
- data/ext/msgpack/unpacker_ext_registry.h +23 -16
- data/lib/msgpack/bigint.rb +69 -0
- data/lib/msgpack/factory.rb +103 -0
- data/lib/msgpack/symbol.rb +21 -4
- data/lib/msgpack/time.rb +1 -1
- data/lib/msgpack/version.rb +1 -1
- data/lib/msgpack.rb +5 -7
- data/msgpack.gemspec +2 -2
- data/spec/bigint_spec.rb +26 -0
- data/spec/cruby/buffer_spec.rb +17 -0
- data/spec/factory_spec.rb +351 -12
- data/spec/msgpack_spec.rb +1 -1
- data/spec/packer_spec.rb +18 -0
- data/spec/spec_helper.rb +20 -3
- data/spec/timestamp_spec.rb +38 -0
- data/spec/unpacker_spec.rb +54 -4
- metadata +25 -41
- data/.travis.yml +0 -39
- data/bench/pack.rb +0 -23
- data/bench/pack_log.rb +0 -33
- data/bench/pack_log_long.rb +0 -65
- data/bench/pack_symbols.rb +0 -28
- data/bench/run.sh +0 -14
- data/bench/run_long.sh +0 -35
- data/bench/run_symbols.sh +0 -26
- data/bench/unpack.rb +0 -21
- data/bench/unpack_log.rb +0 -34
- data/bench/unpack_log_long.rb +0 -67
data/lib/msgpack/factory.rb
CHANGED
@@ -77,5 +77,108 @@ module MessagePack
|
|
77
77
|
packer.full_pack
|
78
78
|
end
|
79
79
|
alias :pack :dump
|
80
|
+
|
81
|
+
def pool(size = 1, **options)
|
82
|
+
Pool.new(
|
83
|
+
frozen? ? self : dup.freeze,
|
84
|
+
size,
|
85
|
+
options.empty? ? nil : options,
|
86
|
+
)
|
87
|
+
end
|
88
|
+
|
89
|
+
class Pool
|
90
|
+
if RUBY_ENGINE == "ruby"
|
91
|
+
class AbstractPool
|
92
|
+
def initialize(size, &block)
|
93
|
+
@size = size
|
94
|
+
@new_member = block
|
95
|
+
@members = []
|
96
|
+
end
|
97
|
+
|
98
|
+
def checkout
|
99
|
+
@members.pop || @new_member.call
|
100
|
+
end
|
101
|
+
|
102
|
+
def checkin(member)
|
103
|
+
# If the pool is already full, we simply drop the extra member.
|
104
|
+
# This is because contrary to a connection pool, creating an extra instance
|
105
|
+
# is extremely unlikely to cause some kind of resource exhaustion.
|
106
|
+
#
|
107
|
+
# We could cycle the members (keep the newer one) but first It's more work and second
|
108
|
+
# the older member might have been created pre-fork, so it might be at least partially
|
109
|
+
# in shared memory.
|
110
|
+
if member && @members.size < @size
|
111
|
+
member.reset
|
112
|
+
@members << member
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
else
|
117
|
+
class AbstractPool
|
118
|
+
def initialize(size, &block)
|
119
|
+
@size = size
|
120
|
+
@new_member = block
|
121
|
+
@members = []
|
122
|
+
@mutex = Mutex.new
|
123
|
+
end
|
124
|
+
|
125
|
+
def checkout
|
126
|
+
@mutex.synchronize { @members.pop } || @new_member.call
|
127
|
+
end
|
128
|
+
|
129
|
+
def checkin(member)
|
130
|
+
@mutex.synchronize do
|
131
|
+
if member && @members.size < @size
|
132
|
+
member.reset
|
133
|
+
@members << member
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
class PackerPool < AbstractPool
|
141
|
+
private
|
142
|
+
|
143
|
+
def reset(packer)
|
144
|
+
packer.clear
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
class UnpackerPool < AbstractPool
|
149
|
+
private
|
150
|
+
|
151
|
+
def reset(unpacker)
|
152
|
+
unpacker.reset
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def initialize(factory, size, options = nil)
|
157
|
+
options = nil if !options || options.empty?
|
158
|
+
@factory = factory
|
159
|
+
@packers = PackerPool.new(size) { factory.packer(options) }
|
160
|
+
@unpackers = UnpackerPool.new(size) { factory.unpacker(options) }
|
161
|
+
end
|
162
|
+
|
163
|
+
def load(data)
|
164
|
+
unpacker = @unpackers.checkout
|
165
|
+
begin
|
166
|
+
unpacker.feed_reference(data)
|
167
|
+
unpacker.full_unpack
|
168
|
+
ensure
|
169
|
+
@unpackers.checkin(unpacker)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def dump(object)
|
174
|
+
packer = @packers.checkout
|
175
|
+
begin
|
176
|
+
packer.write(object)
|
177
|
+
packer.full_pack
|
178
|
+
ensure
|
179
|
+
@packers.checkin(packer)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
80
183
|
end
|
81
184
|
end
|
data/lib/msgpack/symbol.rb
CHANGED
@@ -1,9 +1,26 @@
|
|
1
1
|
class Symbol
|
2
|
-
|
3
|
-
|
2
|
+
# to_msgpack_ext is supposed to return a binary string.
|
3
|
+
# The canonical way to do it for symbols would be:
|
4
|
+
# [to_s].pack('A*')
|
5
|
+
# However in this instance we can take a shortcut
|
6
|
+
if method_defined?(:name)
|
7
|
+
alias_method :to_msgpack_ext, :name
|
8
|
+
else
|
9
|
+
alias_method :to_msgpack_ext, :to_s
|
4
10
|
end
|
5
11
|
|
6
12
|
def self.from_msgpack_ext(data)
|
7
|
-
|
13
|
+
# from_msgpack_ext is supposed to parse a binary string.
|
14
|
+
# The canonical way to do it for symbols would be:
|
15
|
+
# data.unpack1('A*').to_sym
|
16
|
+
# However in this instance we can take a shortcut
|
17
|
+
|
18
|
+
# We assume the string encoding is UTF-8, and let Ruby create either
|
19
|
+
# an ASCII symbol or UTF-8 symbol.
|
20
|
+
data.force_encoding(Encoding::UTF_8).to_sym
|
21
|
+
rescue EncodingError
|
22
|
+
# If somehow the string wasn't valid UTF-8 not valid ASCII, we fallback
|
23
|
+
# to what has been the historical behavior of creating a binary symbol
|
24
|
+
data.force_encoding(Encoding::BINARY).to_sym
|
8
25
|
end
|
9
|
-
end
|
26
|
+
end
|
data/lib/msgpack/time.rb
CHANGED
data/lib/msgpack/version.rb
CHANGED
data/lib/msgpack.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
1
|
require "msgpack/version"
|
2
2
|
|
3
3
|
if defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby" # This is same with `/java/ =~ RUBY_VERSION`
|
4
|
-
require "java"
|
5
4
|
require "msgpack/msgpack.jar"
|
6
|
-
org.msgpack.jruby.MessagePackLibrary
|
5
|
+
JRuby::Util.load_ext("org.msgpack.jruby.MessagePackLibrary")
|
7
6
|
else
|
8
7
|
require "msgpack/msgpack"
|
9
8
|
end
|
@@ -18,16 +17,15 @@ require "msgpack/time"
|
|
18
17
|
|
19
18
|
module MessagePack
|
20
19
|
DefaultFactory = MessagePack::Factory.new
|
21
|
-
DEFAULT_EMPTY_PARAMS = {}.freeze
|
22
20
|
|
23
21
|
def load(src, param = nil)
|
24
22
|
unpacker = nil
|
25
23
|
|
26
24
|
if src.is_a? String
|
27
|
-
unpacker = DefaultFactory.unpacker param
|
25
|
+
unpacker = DefaultFactory.unpacker param
|
28
26
|
unpacker.feed_reference src
|
29
27
|
else
|
30
|
-
unpacker = DefaultFactory.unpacker src, param
|
28
|
+
unpacker = DefaultFactory.unpacker src, param
|
31
29
|
end
|
32
30
|
|
33
31
|
unpacker.full_unpack
|
@@ -37,8 +35,8 @@ module MessagePack
|
|
37
35
|
module_function :load
|
38
36
|
module_function :unpack
|
39
37
|
|
40
|
-
def pack(v,
|
41
|
-
packer = DefaultFactory.packer(
|
38
|
+
def pack(v, io = nil, options = nil)
|
39
|
+
packer = DefaultFactory.packer(io, options)
|
42
40
|
packer.write v
|
43
41
|
packer.full_pack
|
44
42
|
end
|
data/msgpack.gemspec
CHANGED
@@ -18,14 +18,14 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.files = `git ls-files`.split("\n")
|
19
19
|
s.extensions = ["ext/msgpack/extconf.rb"]
|
20
20
|
end
|
21
|
-
s.test_files = `git ls-files -- {test,spec}/*`.split("\n")
|
22
21
|
|
23
22
|
s.required_ruby_version = ">= 2.4"
|
24
23
|
|
25
24
|
s.add_development_dependency 'bundler'
|
26
25
|
s.add_development_dependency 'rake'
|
27
|
-
s.add_development_dependency 'rake-compiler'
|
26
|
+
s.add_development_dependency 'rake-compiler', ['>= 1.1.9']
|
28
27
|
s.add_development_dependency 'rspec', ['~> 3.3']
|
29
28
|
s.add_development_dependency 'yard'
|
30
29
|
s.add_development_dependency 'json'
|
30
|
+
s.add_development_dependency 'benchmark-ips', ['~> 2.10.0']
|
31
31
|
end
|
data/spec/bigint_spec.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MessagePack::Bigint do
|
4
|
+
it 'serialize and deserialize arbitrary sized integer' do
|
5
|
+
[
|
6
|
+
1,
|
7
|
+
-1,
|
8
|
+
120938120391283122132313,
|
9
|
+
-21903120391203912391023920332103,
|
10
|
+
210290021321301203912933021323,
|
11
|
+
].each do |int|
|
12
|
+
expect(MessagePack::Bigint.from_msgpack_ext(MessagePack::Bigint.to_msgpack_ext(int))).to be == int
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'has a stable format' do
|
17
|
+
{
|
18
|
+
120938120391283122132313 => "\x00\x9F\xF4UY\x11\x92\x9A?\x00\x00\x19\x9C".b,
|
19
|
+
-21903120391203912391023920332103 => "\x01/\xB2\xBDG\xBD\xDE\xAA\xEBt\xCC\x8A\xC1\x00\x00\x01\x14".b,
|
20
|
+
210290021321301203912933021323 => "\x00\xC4\xD8\x96\x8Bm\xCB\xC7\x03\xA7{\xD4\"\x00\x00\x00\x02".b,
|
21
|
+
}.each do |int, payload|
|
22
|
+
expect(MessagePack::Bigint.to_msgpack_ext(int)).to be == payload
|
23
|
+
expect(MessagePack::Bigint.from_msgpack_ext(payload)).to be == int
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/spec/cruby/buffer_spec.rb
CHANGED
@@ -572,4 +572,21 @@ describe Buffer do
|
|
572
572
|
end
|
573
573
|
}
|
574
574
|
end
|
575
|
+
|
576
|
+
it "defines a function for ObjectSpace.memsize_of" do
|
577
|
+
skip "JRuby doesn't support ObjectSpace.memsize_of" if IS_JRUBY
|
578
|
+
|
579
|
+
buffer = MessagePack::Buffer.new
|
580
|
+
empty_size = ObjectSpace.memsize_of(buffer)
|
581
|
+
expect(empty_size).to be < 400
|
582
|
+
10.times do
|
583
|
+
buffer << "a" * 500
|
584
|
+
end
|
585
|
+
memsize = ObjectSpace.memsize_of(buffer)
|
586
|
+
expect(memsize).to be > empty_size
|
587
|
+
buffer.read(10)
|
588
|
+
expect(ObjectSpace.memsize_of(buffer)).to be == memsize
|
589
|
+
buffer.read_all
|
590
|
+
expect(ObjectSpace.memsize_of(buffer)).to be == empty_size
|
591
|
+
end
|
575
592
|
end
|
data/spec/factory_spec.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# encoding: ascii-8bit
|
2
1
|
require 'spec_helper'
|
3
2
|
|
4
3
|
describe MessagePack::Factory do
|
@@ -42,6 +41,21 @@ describe MessagePack::Factory do
|
|
42
41
|
unpacker.feed(MessagePack::ExtensionValue.new(1, 'a').to_msgpack)
|
43
42
|
expect{ unpacker.read }.to raise_error(MessagePack::UnknownExtTypeError)
|
44
43
|
end
|
44
|
+
|
45
|
+
it 'does not share the extension registry with unpackers' do
|
46
|
+
subject.register_type(0x00, Symbol)
|
47
|
+
expect do
|
48
|
+
unpacker = subject.unpacker
|
49
|
+
expect do
|
50
|
+
unpacker.register_type(0x01) {}
|
51
|
+
end.to change { unpacker.registered_types }
|
52
|
+
|
53
|
+
second_unpacker = subject.unpacker
|
54
|
+
expect do
|
55
|
+
second_unpacker.register_type(0x01) {}
|
56
|
+
end.to_not change { unpacker.registered_types }
|
57
|
+
end.to_not change { subject.registered_types }
|
58
|
+
end
|
45
59
|
end
|
46
60
|
|
47
61
|
describe '#dump and #load' do
|
@@ -255,34 +269,231 @@ describe MessagePack::Factory do
|
|
255
269
|
subject { factory.packer.pack(value).to_s }
|
256
270
|
before { stub_const('Value', Class.new{ include Mod }) }
|
257
271
|
let(:value) { Value.new }
|
258
|
-
it { is_expected.to eq "\xC7\x0F\x01value_msgpacked" }
|
272
|
+
it { is_expected.to eq "\xC7\x0F\x01value_msgpacked".force_encoding(Encoding::BINARY) }
|
259
273
|
end
|
260
274
|
|
261
275
|
describe "packing an object which has been extended by the module" do
|
262
276
|
subject { factory.packer.pack(object).to_s }
|
263
277
|
let(:object) { Object.new.extend Mod }
|
264
|
-
it { is_expected.to eq "\xC7\x0F\x01value_msgpacked" }
|
278
|
+
it { is_expected.to eq "\xC7\x0F\x01value_msgpacked".force_encoding(Encoding::BINARY) }
|
265
279
|
end
|
266
280
|
|
267
281
|
describe "unpacking with the module" do
|
268
|
-
subject { factory.unpacker.feed("\xC7\x06\x01module").unpack }
|
282
|
+
subject { factory.unpacker.feed("\xC7\x06\x01module".force_encoding(Encoding::BINARY)).unpack }
|
269
283
|
it { is_expected.to eq "unpacked module" }
|
270
284
|
end
|
271
285
|
end
|
286
|
+
|
287
|
+
describe "registering an ext type for Integer" do
|
288
|
+
let(:factory) { described_class.new }
|
289
|
+
let(:bigint) { 10**150 }
|
290
|
+
|
291
|
+
it 'does not work by default without passing `oversized_integer_extension: true`' do
|
292
|
+
factory.register_type(0x01, Integer, packer: :to_s, unpacker: method(:Integer))
|
293
|
+
|
294
|
+
expect do
|
295
|
+
factory.dump(bigint)
|
296
|
+
end.to raise_error RangeError
|
297
|
+
end
|
298
|
+
|
299
|
+
it 'raises ArgumentError if the type is not Integer' do
|
300
|
+
expect do
|
301
|
+
factory.register_type(0x01, MyType, packer: :to_s, unpacker: method(:Integer), oversized_integer_extension: true)
|
302
|
+
end.to raise_error(ArgumentError)
|
303
|
+
end
|
304
|
+
|
305
|
+
it 'invokes the packer if registered with `oversized_integer_extension: true`' do
|
306
|
+
factory.register_type(0x01, Integer, packer: :to_s, unpacker: method(:Integer), oversized_integer_extension: true)
|
307
|
+
|
308
|
+
expect(factory.load(factory.dump(bigint))).to be == bigint
|
309
|
+
end
|
310
|
+
|
311
|
+
it 'does not use the oversized_integer_extension packer for integers fitting in native types' do
|
312
|
+
factory.register_type(
|
313
|
+
0x01,
|
314
|
+
Integer,
|
315
|
+
packer: ->(int) { raise NotImplementedError },
|
316
|
+
unpacker: ->(payload) { raise NotImplementedError },
|
317
|
+
oversized_integer_extension: true
|
318
|
+
)
|
319
|
+
|
320
|
+
expect(factory.dump(42)).to eq(MessagePack.dump(42))
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
describe "registering ext type with recursive serialization" do
|
325
|
+
before do
|
326
|
+
stub_const("Point", Struct.new(:x, :y, :z))
|
327
|
+
end
|
328
|
+
|
329
|
+
it 'can receive the packer as argument (proc)' do
|
330
|
+
factory = MessagePack::Factory.new
|
331
|
+
factory.register_type(0x00, Symbol)
|
332
|
+
factory.register_type(
|
333
|
+
0x01,
|
334
|
+
Point,
|
335
|
+
packer: ->(point, packer) do
|
336
|
+
packer.write(point.to_h)
|
337
|
+
nil
|
338
|
+
end,
|
339
|
+
unpacker: ->(unpacker) do
|
340
|
+
attrs = unpacker.read
|
341
|
+
Point.new(attrs.fetch(:x), attrs.fetch(:y), attrs.fetch(:z))
|
342
|
+
end,
|
343
|
+
recursive: true,
|
344
|
+
)
|
345
|
+
|
346
|
+
point = Point.new(1, 2, 3)
|
347
|
+
payload = factory.dump(point)
|
348
|
+
expect(factory.load(payload)).to be == point
|
349
|
+
end
|
350
|
+
|
351
|
+
it 'can receive the packer as argument (Method)' do
|
352
|
+
mod = Module.new
|
353
|
+
mod.define_singleton_method(:packer) do |point, packer|
|
354
|
+
packer.write(point.to_h)
|
355
|
+
nil
|
356
|
+
end
|
357
|
+
|
358
|
+
mod.define_singleton_method(:unpacker) do |unpacker|
|
359
|
+
attrs = unpacker.read
|
360
|
+
Point.new(attrs.fetch(:x), attrs.fetch(:y), attrs.fetch(:z))
|
361
|
+
end
|
362
|
+
|
363
|
+
factory = MessagePack::Factory.new
|
364
|
+
factory.register_type(0x00, Symbol)
|
365
|
+
factory.register_type(
|
366
|
+
0x01,
|
367
|
+
Point,
|
368
|
+
packer: mod.method(:packer),
|
369
|
+
unpacker: mod.method(:unpacker),
|
370
|
+
recursive: true,
|
371
|
+
)
|
372
|
+
|
373
|
+
point = Point.new(1, 2, 3)
|
374
|
+
payload = factory.dump(point)
|
375
|
+
expect(factory.load(payload)).to be == point
|
376
|
+
end
|
377
|
+
|
378
|
+
it 'respect message pack format' do
|
379
|
+
factory = MessagePack::Factory.new
|
380
|
+
factory.register_type(0x00, Symbol)
|
381
|
+
factory.register_type(
|
382
|
+
0x01,
|
383
|
+
Point,
|
384
|
+
packer: ->(point, packer) do
|
385
|
+
packer.write(point.to_a)
|
386
|
+
nil
|
387
|
+
end,
|
388
|
+
unpacker: ->(unpacker) do
|
389
|
+
attrs = unpacker.read
|
390
|
+
Point.new(*attrs)
|
391
|
+
end,
|
392
|
+
recursive: true,
|
393
|
+
)
|
394
|
+
|
395
|
+
point = Point.new(1, 2, 3)
|
396
|
+
expect(factory.dump(point)).to be == "\xD6\x01".b + MessagePack.dump([1, 2, 3])
|
397
|
+
end
|
398
|
+
|
399
|
+
it 'sets the correct length' do
|
400
|
+
factory = MessagePack::Factory.new
|
401
|
+
factory.register_type(0x00, Symbol)
|
402
|
+
factory.register_type(
|
403
|
+
0x01,
|
404
|
+
Point,
|
405
|
+
packer: ->(point, packer) do
|
406
|
+
packer.write(point.to_h)
|
407
|
+
nil
|
408
|
+
end,
|
409
|
+
unpacker: ->(unpacker) do
|
410
|
+
attrs = unpacker.read
|
411
|
+
Point.new(attrs.fetch(:x), attrs.fetch(:y), attrs.fetch(:z))
|
412
|
+
end,
|
413
|
+
recursive: true,
|
414
|
+
)
|
415
|
+
|
416
|
+
point = Point.new(1, 2, 3)
|
417
|
+
payload = factory.dump([1, point, 3])
|
418
|
+
|
419
|
+
obj = MessagePack::Factory.new.load(payload, allow_unknown_ext: true)
|
420
|
+
expect(obj).to be == [
|
421
|
+
1,
|
422
|
+
MessagePack::ExtensionValue.new(1, factory.dump(x: 1, y: 2, z: 3)),
|
423
|
+
3,
|
424
|
+
]
|
425
|
+
|
426
|
+
expect(factory.load(payload)).to be == [
|
427
|
+
1,
|
428
|
+
Point.new(1, 2, 3),
|
429
|
+
3,
|
430
|
+
]
|
431
|
+
end
|
432
|
+
|
433
|
+
it 'can be nested' do
|
434
|
+
factory = MessagePack::Factory.new
|
435
|
+
factory.register_type(
|
436
|
+
0x02,
|
437
|
+
Set,
|
438
|
+
packer: ->(set, packer) do
|
439
|
+
packer.write(set.to_a)
|
440
|
+
nil
|
441
|
+
end,
|
442
|
+
unpacker: ->(unpacker) do
|
443
|
+
unpacker.read.to_set
|
444
|
+
end,
|
445
|
+
recursive: true,
|
446
|
+
)
|
447
|
+
|
448
|
+
expected = Set[1, Set[2, Set[3]]]
|
449
|
+
payload = factory.dump(expected)
|
450
|
+
expect(payload).to be == "\xC7\v\x02\x92\x01\xC7\x06\x02\x92\x02\xD5\x02\x91\x03".b
|
451
|
+
expect(factory.load(factory.dump(expected))).to be == expected
|
452
|
+
end
|
453
|
+
end
|
272
454
|
end
|
273
455
|
|
274
456
|
describe 'the special treatment of symbols with ext type' do
|
275
|
-
|
276
|
-
|
457
|
+
def roundtrip(object, options = nil)
|
458
|
+
subject.load(subject.dump(object), options)
|
459
|
+
end
|
277
460
|
|
278
|
-
|
279
|
-
|
280
|
-
|
461
|
+
context 'using the optimized symbol unpacker' do
|
462
|
+
before do
|
463
|
+
skip if IS_JRUBY # JRuby implementation doesn't support the optimized symbols unpacker for now
|
464
|
+
subject.register_type(
|
465
|
+
0x00,
|
466
|
+
::Symbol,
|
467
|
+
packer: :to_msgpack_ext,
|
468
|
+
unpacker: :from_msgpack_ext,
|
469
|
+
optimized_symbols_parsing: true,
|
470
|
+
)
|
471
|
+
end
|
472
|
+
|
473
|
+
it 'lets symbols survive a roundtrip' do
|
474
|
+
expect(roundtrip(:symbol)).to be :symbol
|
475
|
+
end
|
476
|
+
|
477
|
+
it 'works with empty symbol' do
|
478
|
+
expect(roundtrip(:"")).to be :""
|
479
|
+
end
|
480
|
+
|
481
|
+
it 'preserves encoding for ASCII symbols' do
|
482
|
+
expect(:symbol.encoding).to be Encoding::US_ASCII
|
483
|
+
expect(roundtrip(:symbol)).to be :symbol
|
484
|
+
expect(roundtrip(:symbol).encoding).to be Encoding::US_ASCII
|
485
|
+
end
|
486
|
+
|
487
|
+
it 'preserves encoding for UTF-8 symbols' do
|
488
|
+
expect(:"fée".encoding).to be Encoding::UTF_8
|
489
|
+
expect(roundtrip(:"fée").encoding).to be Encoding::UTF_8
|
490
|
+
expect(roundtrip(:"fée")).to be :"fée"
|
491
|
+
end
|
281
492
|
end
|
282
493
|
|
283
494
|
context 'if no ext type is registered for symbols' do
|
284
495
|
it 'converts symbols to string' do
|
285
|
-
expect(
|
496
|
+
expect(roundtrip(:symbol)).to eq 'symbol'
|
286
497
|
end
|
287
498
|
end
|
288
499
|
|
@@ -291,7 +502,41 @@ describe MessagePack::Factory do
|
|
291
502
|
before { subject.register_type(0x00, ::Symbol) }
|
292
503
|
|
293
504
|
it 'lets symbols survive a roundtrip' do
|
294
|
-
expect(
|
505
|
+
expect(roundtrip(:symbol)).to be :symbol
|
506
|
+
end
|
507
|
+
|
508
|
+
it 'works with hash keys' do
|
509
|
+
expect(roundtrip(symbol: 1)).to be == { symbol: 1 }
|
510
|
+
end
|
511
|
+
|
512
|
+
it 'works with frozen: true option' do
|
513
|
+
expect(roundtrip(:symbol, freeze: true)).to be :symbol
|
514
|
+
end
|
515
|
+
|
516
|
+
it 'preserves encoding for ASCII symbols' do
|
517
|
+
expect(:symbol.encoding).to be Encoding::US_ASCII
|
518
|
+
expect(roundtrip(:symbol)).to be :symbol
|
519
|
+
expect(roundtrip(:symbol).encoding).to be Encoding::US_ASCII
|
520
|
+
end
|
521
|
+
|
522
|
+
it 'preserves encoding for UTF-8 symbols' do
|
523
|
+
expect(:"fée".encoding).to be Encoding::UTF_8
|
524
|
+
expect(roundtrip(:"fée")).to be :"fée"
|
525
|
+
expect(roundtrip(:"fée").encoding).to be Encoding::UTF_8
|
526
|
+
end
|
527
|
+
|
528
|
+
it 'does not handle symbols in other encodings' do
|
529
|
+
symbol = "fàe".encode(Encoding::ISO_8859_1).to_sym
|
530
|
+
expect(symbol.encoding).to be Encoding::ISO_8859_1
|
531
|
+
|
532
|
+
if IS_JRUBY
|
533
|
+
# JRuby doesn't quite behave like MRI here.
|
534
|
+
# "fàe".force_encoding(Encoding::BINARY).to_sym is able to lookup the existing ISO-8859-1 symbol
|
535
|
+
# It likely is a JRuby bug.
|
536
|
+
expect(roundtrip(symbol).encoding).to be Encoding::ISO_8859_1
|
537
|
+
else
|
538
|
+
expect(roundtrip(symbol).encoding).to be Encoding::BINARY
|
539
|
+
end
|
295
540
|
end
|
296
541
|
end
|
297
542
|
|
@@ -315,7 +560,7 @@ describe MessagePack::Factory do
|
|
315
560
|
before { subject.register_type(0x00, ::Symbol) }
|
316
561
|
|
317
562
|
it 'lets symbols survive a roundtrip' do
|
318
|
-
expect(
|
563
|
+
expect(roundtrip(:symbol)).to be :symbol
|
319
564
|
end
|
320
565
|
|
321
566
|
after do
|
@@ -342,6 +587,48 @@ describe MessagePack::Factory do
|
|
342
587
|
GC.stress = false
|
343
588
|
end
|
344
589
|
end
|
590
|
+
|
591
|
+
it 'does not crash in recursive extensions' do
|
592
|
+
my_hash_type = Class.new(Hash)
|
593
|
+
factory = MessagePack::Factory.new
|
594
|
+
factory.register_type(7,
|
595
|
+
my_hash_type,
|
596
|
+
packer: ->(value, packer) do
|
597
|
+
packer.write(value.to_h)
|
598
|
+
end,
|
599
|
+
unpacker: ->(unpacker) { my_hash_type.new(unpacker.read) },
|
600
|
+
recursive: true,
|
601
|
+
)
|
602
|
+
|
603
|
+
payload = factory.dump(
|
604
|
+
[my_hash_type.new]
|
605
|
+
)
|
606
|
+
|
607
|
+
begin
|
608
|
+
GC.stress = true
|
609
|
+
factory.load(payload)
|
610
|
+
ensure
|
611
|
+
GC.stress = false
|
612
|
+
end
|
613
|
+
end
|
614
|
+
end
|
615
|
+
|
616
|
+
describe 'memsize' do
|
617
|
+
it 'works on a fresh factory' do
|
618
|
+
skip "JRuby doesn't support ObjectSpace.memsize_of" if IS_JRUBY
|
619
|
+
|
620
|
+
f = MessagePack::Factory.new
|
621
|
+
expect(ObjectSpace.memsize_of(f)).to be_an(Integer)
|
622
|
+
end
|
623
|
+
|
624
|
+
it 'works on a factory with registered types' do
|
625
|
+
skip "JRuby doesn't support ObjectSpace.memsize_of" if IS_JRUBY
|
626
|
+
|
627
|
+
f = MessagePack::Factory.new
|
628
|
+
base_size = ObjectSpace.memsize_of(f)
|
629
|
+
f.register_type(0x0a, Symbol)
|
630
|
+
expect(ObjectSpace.memsize_of(f)).to be > base_size
|
631
|
+
end
|
345
632
|
end
|
346
633
|
|
347
634
|
describe 'DefaultFactory' do
|
@@ -364,4 +651,56 @@ describe MessagePack::Factory do
|
|
364
651
|
expect(MessagePack.unpack(MessagePack.pack(dm2))).to eq(dm2)
|
365
652
|
end
|
366
653
|
end
|
654
|
+
|
655
|
+
describe '#pool' do
|
656
|
+
let(:factory) { described_class.new }
|
657
|
+
|
658
|
+
it 'responds to serializers interface' do
|
659
|
+
pool = factory.pool(1)
|
660
|
+
expect(pool.load(pool.dump(42))).to be == 42
|
661
|
+
end
|
662
|
+
|
663
|
+
it 'types can be registered before the pool is created' do
|
664
|
+
factory.register_type(0x00, Symbol)
|
665
|
+
pool = factory.pool(1)
|
666
|
+
expect(pool.load(pool.dump(:foo))).to be == :foo
|
667
|
+
end
|
668
|
+
|
669
|
+
it 'types cannot be registered after the pool is created' do
|
670
|
+
pool = factory.pool(1)
|
671
|
+
factory.register_type(0x20, ::MyType)
|
672
|
+
|
673
|
+
expect do
|
674
|
+
pool.dump(MyType.new(1, 2))
|
675
|
+
end.to raise_error NoMethodError
|
676
|
+
|
677
|
+
payload = factory.dump(MyType.new(1, 2))
|
678
|
+
expect do
|
679
|
+
pool.load(payload)
|
680
|
+
end.to raise_error MessagePack::UnknownExtTypeError
|
681
|
+
end
|
682
|
+
|
683
|
+
it 'support symbolize_keys: true' do
|
684
|
+
pool = factory.pool(1, symbolize_keys: true)
|
685
|
+
expect(pool.load(pool.dump('foo' => 1))).to be == { foo: 1 }
|
686
|
+
end
|
687
|
+
|
688
|
+
it 'support freeze: true' do
|
689
|
+
pool = factory.pool(1, freeze: true)
|
690
|
+
expect(pool.load(pool.dump('foo'))).to be_frozen
|
691
|
+
end
|
692
|
+
|
693
|
+
it 'is thread safe' do
|
694
|
+
pool = factory.pool(1)
|
695
|
+
|
696
|
+
threads = 10.times.map do
|
697
|
+
Thread.new do
|
698
|
+
1_000.times do |i|
|
699
|
+
expect(pool.load(pool.dump(i))).to be == i
|
700
|
+
end
|
701
|
+
end
|
702
|
+
end
|
703
|
+
threads.each(&:join)
|
704
|
+
end
|
705
|
+
end
|
367
706
|
end
|
data/spec/msgpack_spec.rb
CHANGED
@@ -115,7 +115,7 @@ describe MessagePack do
|
|
115
115
|
expect { MessagePack.pack(self) }.to raise_error(NoMethodError, /^undefined method `to_msgpack'/)
|
116
116
|
end
|
117
117
|
|
118
|
-
it '
|
118
|
+
it 'raises an error on #unpack with garbage' do
|
119
119
|
skip "but nothing was raised. why?"
|
120
120
|
expect { MessagePack.unpack('asdka;sd') }.to raise_error(MessagePack::UnpackError)
|
121
121
|
end
|