msgpack 1.4.3 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yaml +7 -6
  3. data/ChangeLog +18 -0
  4. data/README.md +22 -0
  5. data/Rakefile +1 -2
  6. data/doclib/msgpack/factory.rb +46 -3
  7. data/doclib/msgpack/packer.rb +5 -4
  8. data/doclib/msgpack/unpacker.rb +2 -2
  9. data/ext/java/org/msgpack/jruby/Buffer.java +6 -0
  10. data/ext/java/org/msgpack/jruby/Decoder.java +23 -19
  11. data/ext/java/org/msgpack/jruby/Encoder.java +45 -18
  12. data/ext/java/org/msgpack/jruby/ExtensionRegistry.java +28 -40
  13. data/ext/java/org/msgpack/jruby/Factory.java +40 -5
  14. data/ext/java/org/msgpack/jruby/Packer.java +21 -11
  15. data/ext/java/org/msgpack/jruby/Unpacker.java +44 -22
  16. data/ext/msgpack/buffer.h +2 -2
  17. data/ext/msgpack/buffer_class.c +23 -15
  18. data/ext/msgpack/factory_class.c +78 -16
  19. data/ext/msgpack/packer.c +42 -5
  20. data/ext/msgpack/packer.h +24 -0
  21. data/ext/msgpack/packer_class.c +29 -22
  22. data/ext/msgpack/packer_ext_registry.c +23 -9
  23. data/ext/msgpack/packer_ext_registry.h +38 -31
  24. data/ext/msgpack/unpacker.c +37 -19
  25. data/ext/msgpack/unpacker.h +2 -2
  26. data/ext/msgpack/unpacker_class.c +26 -45
  27. data/ext/msgpack/unpacker_ext_registry.c +40 -16
  28. data/ext/msgpack/unpacker_ext_registry.h +21 -14
  29. data/lib/msgpack/bigint.rb +69 -0
  30. data/lib/msgpack/factory.rb +103 -0
  31. data/lib/msgpack/symbol.rb +8 -1
  32. data/lib/msgpack/version.rb +1 -1
  33. data/lib/msgpack.rb +4 -5
  34. data/msgpack.gemspec +1 -2
  35. data/spec/bigint_spec.rb +26 -0
  36. data/spec/factory_spec.rb +263 -14
  37. data/spec/spec_helper.rb +3 -4
  38. data/spec/timestamp_spec.rb +0 -2
  39. data/spec/unpacker_spec.rb +22 -3
  40. metadata +7 -29
@@ -21,39 +21,46 @@
21
21
  #include "compat.h"
22
22
  #include "ruby.h"
23
23
 
24
+ #define MSGPACK_EXT_RECURSIVE 0b0001
25
+
24
26
  struct msgpack_unpacker_ext_registry_t;
25
27
  typedef struct msgpack_unpacker_ext_registry_t msgpack_unpacker_ext_registry_t;
26
28
 
27
29
  struct msgpack_unpacker_ext_registry_t {
30
+ unsigned int borrow_count;
28
31
  VALUE array[256];
29
- //int bitmap;
30
32
  };
31
33
 
32
34
  void msgpack_unpacker_ext_registry_static_init();
33
35
 
34
36
  void msgpack_unpacker_ext_registry_static_destroy();
35
37
 
36
- void msgpack_unpacker_ext_registry_init(msgpack_unpacker_ext_registry_t* ukrg);
38
+ void msgpack_unpacker_ext_registry_release(msgpack_unpacker_ext_registry_t* ukrg);
37
39
 
38
- static inline void msgpack_unpacker_ext_registry_destroy(msgpack_unpacker_ext_registry_t* ukrg)
39
- { }
40
+ static inline void msgpack_unpacker_ext_registry_borrow(msgpack_unpacker_ext_registry_t* src, msgpack_unpacker_ext_registry_t** dst)
41
+ {
42
+ if (src) {
43
+ src->borrow_count++;
44
+ *dst = src;
45
+ }
46
+ }
40
47
 
41
48
  void msgpack_unpacker_ext_registry_mark(msgpack_unpacker_ext_registry_t* ukrg);
42
49
 
43
- void msgpack_unpacker_ext_registry_dup(msgpack_unpacker_ext_registry_t* src,
44
- msgpack_unpacker_ext_registry_t* dst);
45
-
46
- VALUE msgpack_unpacker_ext_registry_put(msgpack_unpacker_ext_registry_t* ukrg,
47
- VALUE ext_module, int ext_type, VALUE proc, VALUE arg);
50
+ void msgpack_unpacker_ext_registry_put(msgpack_unpacker_ext_registry_t** ukrg,
51
+ VALUE ext_module, int ext_type, int flags, VALUE proc, VALUE arg);
48
52
 
49
53
  static inline VALUE msgpack_unpacker_ext_registry_lookup(msgpack_unpacker_ext_registry_t* ukrg,
50
- int ext_type)
54
+ int ext_type, int* ext_flags_result)
51
55
  {
52
- VALUE e = ukrg->array[ext_type + 128];
53
- if(e == Qnil) {
54
- return Qnil;
56
+ if (ukrg) {
57
+ VALUE entry = ukrg->array[ext_type + 128];
58
+ if (entry != Qnil) {
59
+ *ext_flags_result = FIX2INT(rb_ary_entry(entry, 3));
60
+ return rb_ary_entry(entry, 1);
61
+ }
55
62
  }
56
- return rb_ary_entry(e, 1);
63
+ return Qnil;
57
64
  }
58
65
 
59
66
  #endif
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MessagePack
4
+ module Bigint
5
+ # We split the bigint in 32bits chunks so that individual part fits into
6
+ # a MRI immediate Integer.
7
+ CHUNK_BITLENGTH = 32
8
+ FORMAT = 'CL>*'
9
+
10
+ if Integer.instance_method(:[]).arity != 1 # Ruby 2.7 and newer
11
+ # Starting from Ruby 2.7 we can address arbitrary bitranges inside an Integer with Integer#[]
12
+ # This allows to not allocate any Integer.
13
+ def self.to_msgpack_ext(bigint)
14
+ members = []
15
+
16
+ if bigint < 0
17
+ bigint = -bigint
18
+ members << 1
19
+ else
20
+ members << 0
21
+ end
22
+
23
+ offset = 0
24
+ length = bigint.bit_length
25
+ while offset < length
26
+ members << bigint[offset, CHUNK_BITLENGTH]
27
+ offset += CHUNK_BITLENGTH
28
+ end
29
+
30
+ members.pack(FORMAT)
31
+ end
32
+ else
33
+ # On 2.6 and older since we can't address arbitrary bitranges, so we fallback to shifting the bigint.
34
+ # This means that after each shift, we may allocate another Integer instance.
35
+ BASE = (2**CHUNK_BITLENGTH) - 1
36
+ def self.to_msgpack_ext(bigint)
37
+ members = []
38
+
39
+ if bigint < 0
40
+ bigint = -bigint
41
+ members << 1
42
+ else
43
+ members << 0
44
+ end
45
+
46
+ while bigint > 0
47
+ members << (bigint & BASE)
48
+ bigint = bigint >> CHUNK_BITLENGTH
49
+ end
50
+
51
+ members.pack(FORMAT)
52
+ end
53
+ end
54
+
55
+ def self.from_msgpack_ext(data)
56
+ parts = data.unpack(FORMAT)
57
+
58
+ sign = parts.shift
59
+ sum = parts.pop.to_i
60
+
61
+ parts.reverse_each do |part|
62
+ sum = sum << CHUNK_BITLENGTH
63
+ sum += part
64
+ end
65
+
66
+ sign == 0 ? sum : -sum
67
+ end
68
+ end
69
+ end
@@ -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
@@ -14,6 +14,13 @@ class Symbol
14
14
  # The canonical way to do it for symbols would be:
15
15
  # data.unpack1('A*').to_sym
16
16
  # However in this instance we can take a shortcut
17
- data.to_sym
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
18
25
  end
19
26
  end
@@ -1,5 +1,5 @@
1
1
  module MessagePack
2
- VERSION = "1.4.3"
2
+ VERSION = "1.5.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
@@ -17,16 +17,15 @@ require "msgpack/time"
17
17
 
18
18
  module MessagePack
19
19
  DefaultFactory = MessagePack::Factory.new
20
- DEFAULT_EMPTY_PARAMS = {}.freeze
21
20
 
22
21
  def load(src, param = nil)
23
22
  unpacker = nil
24
23
 
25
24
  if src.is_a? String
26
- unpacker = DefaultFactory.unpacker param || DEFAULT_EMPTY_PARAMS
25
+ unpacker = DefaultFactory.unpacker param
27
26
  unpacker.feed_reference src
28
27
  else
29
- unpacker = DefaultFactory.unpacker src, param || DEFAULT_EMPTY_PARAMS
28
+ unpacker = DefaultFactory.unpacker src, param
30
29
  end
31
30
 
32
31
  unpacker.full_unpack
@@ -36,8 +35,8 @@ module MessagePack
36
35
  module_function :load
37
36
  module_function :unpack
38
37
 
39
- def pack(v, *rest)
40
- packer = DefaultFactory.packer(*rest)
38
+ def pack(v, io = nil, options = nil)
39
+ packer = DefaultFactory.packer(io, options)
41
40
  packer.write v
42
41
  packer.full_pack
43
42
  end
data/msgpack.gemspec CHANGED
@@ -18,13 +18,12 @@ 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'
@@ -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/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,29 +269,166 @@ 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
+ end
426
+ end
272
427
  end
273
428
 
274
429
  describe 'the special treatment of symbols with ext type' do
275
- let(:packer) { subject.packer }
276
- let(:unpacker) { subject.unpacker }
277
-
278
- def symbol_after_roundtrip
279
- packed_symbol = packer.pack(:symbol).to_s
280
- unpacker.feed(packed_symbol).unpack
430
+ def roundtrip(object, options = nil)
431
+ subject.load(subject.dump(object), options)
281
432
  end
282
433
 
283
434
  context 'using the optimized symbol unpacker' do
@@ -293,13 +444,25 @@ describe MessagePack::Factory do
293
444
  end
294
445
 
295
446
  it 'lets symbols survive a roundtrip' do
296
- expect(symbol_after_roundtrip).to be :symbol
447
+ expect(roundtrip(:symbol)).to be :symbol
448
+ end
449
+
450
+ it 'preserves encoding for ASCII symbols' do
451
+ expect(:symbol.encoding).to be Encoding::US_ASCII
452
+ expect(roundtrip(:symbol)).to be :symbol
453
+ expect(roundtrip(:symbol).encoding).to be Encoding::US_ASCII
454
+ end
455
+
456
+ it 'preserves encoding for UTF-8 symbols' do
457
+ expect(:"fée".encoding).to be Encoding::UTF_8
458
+ expect(roundtrip(:"fée").encoding).to be Encoding::UTF_8
459
+ expect(roundtrip(:"fée")).to be :"fée"
297
460
  end
298
461
  end
299
462
 
300
463
  context 'if no ext type is registered for symbols' do
301
464
  it 'converts symbols to string' do
302
- expect(symbol_after_roundtrip).to eq 'symbol'
465
+ expect(roundtrip(:symbol)).to eq 'symbol'
303
466
  end
304
467
  end
305
468
 
@@ -308,7 +471,41 @@ describe MessagePack::Factory do
308
471
  before { subject.register_type(0x00, ::Symbol) }
309
472
 
310
473
  it 'lets symbols survive a roundtrip' do
311
- expect(symbol_after_roundtrip).to be :symbol
474
+ expect(roundtrip(:symbol)).to be :symbol
475
+ end
476
+
477
+ it 'works with hash keys' do
478
+ expect(roundtrip(symbol: 1)).to be == { symbol: 1 }
479
+ end
480
+
481
+ it 'works with frozen: true option' do
482
+ expect(roundtrip(:symbol, freeze: true)).to be :symbol
483
+ end
484
+
485
+ it 'preserves encoding for ASCII symbols' do
486
+ expect(:symbol.encoding).to be Encoding::US_ASCII
487
+ expect(roundtrip(:symbol)).to be :symbol
488
+ expect(roundtrip(:symbol).encoding).to be Encoding::US_ASCII
489
+ end
490
+
491
+ it 'preserves encoding for UTF-8 symbols' do
492
+ expect(:"fée".encoding).to be Encoding::UTF_8
493
+ expect(roundtrip(:"fée")).to be :"fée"
494
+ expect(roundtrip(:"fée").encoding).to be Encoding::UTF_8
495
+ end
496
+
497
+ it 'does not handle symbols in other encodings' do
498
+ symbol = "fàe".encode(Encoding::ISO_8859_1).to_sym
499
+ expect(symbol.encoding).to be Encoding::ISO_8859_1
500
+
501
+ if IS_JRUBY
502
+ # JRuby doesn't quite behave like MRI here.
503
+ # "fàe".force_encoding(Encoding::BINARY).to_sym is able to lookup the existing ISO-8859-1 symbol
504
+ # It likely is a JRuby bug.
505
+ expect(roundtrip(symbol).encoding).to be Encoding::ISO_8859_1
506
+ else
507
+ expect(roundtrip(symbol).encoding).to be Encoding::BINARY
508
+ end
312
509
  end
313
510
  end
314
511
 
@@ -332,7 +529,7 @@ describe MessagePack::Factory do
332
529
  before { subject.register_type(0x00, ::Symbol) }
333
530
 
334
531
  it 'lets symbols survive a roundtrip' do
335
- expect(symbol_after_roundtrip).to be :symbol
532
+ expect(roundtrip(:symbol)).to be :symbol
336
533
  end
337
534
 
338
535
  after do
@@ -381,4 +578,56 @@ describe MessagePack::Factory do
381
578
  expect(MessagePack.unpack(MessagePack.pack(dm2))).to eq(dm2)
382
579
  end
383
580
  end
581
+
582
+ describe '#pool' do
583
+ let(:factory) { described_class.new }
584
+
585
+ it 'responds to serializers interface' do
586
+ pool = factory.pool(1)
587
+ expect(pool.load(pool.dump(42))).to be == 42
588
+ end
589
+
590
+ it 'types can be registered before the pool is created' do
591
+ factory.register_type(0x00, Symbol)
592
+ pool = factory.pool(1)
593
+ expect(pool.load(pool.dump(:foo))).to be == :foo
594
+ end
595
+
596
+ it 'types cannot be registered after the pool is created' do
597
+ pool = factory.pool(1)
598
+ factory.register_type(0x20, ::MyType)
599
+
600
+ expect do
601
+ pool.dump(MyType.new(1, 2))
602
+ end.to raise_error NoMethodError
603
+
604
+ payload = factory.dump(MyType.new(1, 2))
605
+ expect do
606
+ pool.load(payload)
607
+ end.to raise_error MessagePack::UnknownExtTypeError
608
+ end
609
+
610
+ it 'support symbolize_keys: true' do
611
+ pool = factory.pool(1, symbolize_keys: true)
612
+ expect(pool.load(pool.dump('foo' => 1))).to be == { foo: 1 }
613
+ end
614
+
615
+ it 'support freeze: true' do
616
+ pool = factory.pool(1, freeze: true)
617
+ expect(pool.load(pool.dump('foo'))).to be_frozen
618
+ end
619
+
620
+ it 'is thread safe' do
621
+ pool = factory.pool(1)
622
+
623
+ threads = 10.times.map do
624
+ Thread.new do
625
+ 1_000.times do |i|
626
+ expect(pool.load(pool.dump(i))).to be == i
627
+ end
628
+ end
629
+ end
630
+ threads.each(&:join)
631
+ end
632
+ end
384
633
  end
data/spec/spec_helper.rb CHANGED
@@ -14,6 +14,7 @@ if ENV['GC_STRESS']
14
14
  end
15
15
 
16
16
  require 'msgpack'
17
+ require "msgpack/bigint"
17
18
 
18
19
  if GC.respond_to?(:verify_compaction_references)
19
20
  # This method was added in Ruby 3.0.0. Calling it this way asks the GC to
@@ -25,9 +26,7 @@ if GC.respond_to?(:auto_compact=)
25
26
  GC.auto_compact = true
26
27
  end
27
28
 
28
- def java?
29
- /java/ =~ RUBY_PLATFORM
30
- end
29
+ IS_JRUBY = RUBY_ENGINE == 'jruby'
31
30
 
32
31
  # checking if Hash#[]= (rb_hash_aset) dedupes string keys
33
32
  def automatic_string_keys_deduplication?
@@ -46,7 +45,7 @@ def string_deduplication?
46
45
  (-r1).equal?(-r2)
47
46
  end
48
47
 
49
- if java?
48
+ if IS_JRUBY
50
49
  RSpec.configure do |c|
51
50
  c.treat_symbols_as_metadata_keys_with_true_values = true
52
51
  c.filter_run_excluding :encodings => !(defined? Encoding)