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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yaml +57 -0
  3. data/ChangeLog +60 -0
  4. data/README.md +25 -1
  5. data/Rakefile +1 -2
  6. data/bench/bench.rb +78 -0
  7. data/bin/console +8 -0
  8. data/doclib/msgpack/factory.rb +47 -3
  9. data/doclib/msgpack/packer.rb +5 -4
  10. data/doclib/msgpack/unpacker.rb +2 -2
  11. data/ext/java/org/msgpack/jruby/Buffer.java +23 -16
  12. data/ext/java/org/msgpack/jruby/Decoder.java +29 -21
  13. data/ext/java/org/msgpack/jruby/Encoder.java +68 -30
  14. data/ext/java/org/msgpack/jruby/ExtensionRegistry.java +37 -49
  15. data/ext/java/org/msgpack/jruby/ExtensionValue.java +5 -8
  16. data/ext/java/org/msgpack/jruby/Factory.java +47 -7
  17. data/ext/java/org/msgpack/jruby/Packer.java +29 -17
  18. data/ext/java/org/msgpack/jruby/Unpacker.java +66 -42
  19. data/ext/msgpack/buffer.c +38 -57
  20. data/ext/msgpack/buffer.h +19 -10
  21. data/ext/msgpack/buffer_class.c +90 -52
  22. data/ext/msgpack/compat.h +0 -99
  23. data/ext/msgpack/extconf.rb +9 -22
  24. data/ext/msgpack/factory_class.c +133 -43
  25. data/ext/msgpack/packer.c +60 -36
  26. data/ext/msgpack/packer.h +27 -18
  27. data/ext/msgpack/packer_class.c +84 -77
  28. data/ext/msgpack/packer_class.h +11 -0
  29. data/ext/msgpack/packer_ext_registry.c +24 -32
  30. data/ext/msgpack/packer_ext_registry.h +40 -33
  31. data/ext/msgpack/sysdep.h +5 -2
  32. data/ext/msgpack/unpacker.c +128 -97
  33. data/ext/msgpack/unpacker.h +17 -10
  34. data/ext/msgpack/unpacker_class.c +75 -80
  35. data/ext/msgpack/unpacker_class.h +11 -0
  36. data/ext/msgpack/unpacker_ext_registry.c +42 -18
  37. data/ext/msgpack/unpacker_ext_registry.h +23 -16
  38. data/lib/msgpack/bigint.rb +69 -0
  39. data/lib/msgpack/factory.rb +103 -0
  40. data/lib/msgpack/symbol.rb +21 -4
  41. data/lib/msgpack/time.rb +1 -1
  42. data/lib/msgpack/version.rb +1 -1
  43. data/lib/msgpack.rb +5 -7
  44. data/msgpack.gemspec +2 -2
  45. data/spec/bigint_spec.rb +26 -0
  46. data/spec/cruby/buffer_spec.rb +17 -0
  47. data/spec/factory_spec.rb +351 -12
  48. data/spec/msgpack_spec.rb +1 -1
  49. data/spec/packer_spec.rb +18 -0
  50. data/spec/spec_helper.rb +20 -3
  51. data/spec/timestamp_spec.rb +38 -0
  52. data/spec/unpacker_spec.rb +54 -4
  53. metadata +25 -41
  54. data/.travis.yml +0 -39
  55. data/bench/pack.rb +0 -23
  56. data/bench/pack_log.rb +0 -33
  57. data/bench/pack_log_long.rb +0 -65
  58. data/bench/pack_symbols.rb +0 -28
  59. data/bench/run.sh +0 -14
  60. data/bench/run_long.sh +0 -35
  61. data/bench/run_symbols.sh +0 -26
  62. data/bench/unpack.rb +0 -21
  63. data/bench/unpack_log.rb +0 -34
  64. data/bench/unpack_log_long.rb +0 -67
@@ -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
@@ -1,9 +1,26 @@
1
1
  class Symbol
2
- def to_msgpack_ext
3
- [to_s].pack('A*')
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
- data.unpack('A*').first.to_sym
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
@@ -18,7 +18,7 @@ module MessagePack
18
18
  else
19
19
  lambda do |payload|
20
20
  tv = MessagePack::Timestamp.from_msgpack_ext(payload)
21
- ::Time.at(tv.sec, tv.nsec / 1000.0)
21
+ ::Time.at(tv.sec, tv.nsec / 1000.0r)
22
22
  end
23
23
  end
24
24
 
@@ -1,5 +1,5 @@
1
1
  module MessagePack
2
- VERSION = "1.4.2"
2
+ VERSION = "1.6.0"
3
3
  # Note for maintainers:
4
4
  # Don't miss building/releasing the JRuby version (rake buld:java)
5
5
  # See "How to build -java rubygems" in README for more details.
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.new.load(JRuby.runtime, false)
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 || DEFAULT_EMPTY_PARAMS
25
+ unpacker = DefaultFactory.unpacker param
28
26
  unpacker.feed_reference src
29
27
  else
30
- unpacker = DefaultFactory.unpacker src, param || DEFAULT_EMPTY_PARAMS
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, *rest)
41
- packer = DefaultFactory.packer(*rest)
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
@@ -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
@@ -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
- let(:packer) { subject.packer }
276
- let(:unpacker) { subject.unpacker }
457
+ def roundtrip(object, options = nil)
458
+ subject.load(subject.dump(object), options)
459
+ end
277
460
 
278
- def symbol_after_roundtrip
279
- packed_symbol = packer.pack(:symbol).to_s
280
- unpacker.feed(packed_symbol).unpack
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(symbol_after_roundtrip).to eq 'symbol'
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(symbol_after_roundtrip).to be :symbol
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(symbol_after_roundtrip).to be :symbol
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 'rasies an error on #unpack with garbage' do
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