msgpack 1.4.3 → 1.5.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 +7 -6
- data/ChangeLog +18 -0
- data/README.md +22 -0
- data/Rakefile +1 -2
- data/doclib/msgpack/factory.rb +46 -3
- data/doclib/msgpack/packer.rb +5 -4
- data/doclib/msgpack/unpacker.rb +2 -2
- data/ext/java/org/msgpack/jruby/Buffer.java +6 -0
- data/ext/java/org/msgpack/jruby/Decoder.java +23 -19
- data/ext/java/org/msgpack/jruby/Encoder.java +45 -18
- data/ext/java/org/msgpack/jruby/ExtensionRegistry.java +28 -40
- data/ext/java/org/msgpack/jruby/Factory.java +40 -5
- data/ext/java/org/msgpack/jruby/Packer.java +21 -11
- data/ext/java/org/msgpack/jruby/Unpacker.java +44 -22
- data/ext/msgpack/buffer.h +2 -2
- data/ext/msgpack/buffer_class.c +23 -15
- data/ext/msgpack/factory_class.c +78 -16
- data/ext/msgpack/packer.c +42 -5
- data/ext/msgpack/packer.h +24 -0
- data/ext/msgpack/packer_class.c +29 -22
- data/ext/msgpack/packer_ext_registry.c +23 -9
- data/ext/msgpack/packer_ext_registry.h +38 -31
- data/ext/msgpack/unpacker.c +37 -19
- data/ext/msgpack/unpacker.h +2 -2
- data/ext/msgpack/unpacker_class.c +26 -45
- data/ext/msgpack/unpacker_ext_registry.c +40 -16
- data/ext/msgpack/unpacker_ext_registry.h +21 -14
- data/lib/msgpack/bigint.rb +69 -0
- data/lib/msgpack/factory.rb +103 -0
- data/lib/msgpack/symbol.rb +8 -1
- data/lib/msgpack/version.rb +1 -1
- data/lib/msgpack.rb +4 -5
- data/msgpack.gemspec +1 -2
- data/spec/bigint_spec.rb +26 -0
- data/spec/factory_spec.rb +263 -14
- data/spec/spec_helper.rb +3 -4
- data/spec/timestamp_spec.rb +0 -2
- data/spec/unpacker_spec.rb +22 -3
- 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
|
38
|
+
void msgpack_unpacker_ext_registry_release(msgpack_unpacker_ext_registry_t* ukrg);
|
37
39
|
|
38
|
-
static inline void
|
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
|
44
|
-
|
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
|
-
|
53
|
-
|
54
|
-
|
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
|
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
|
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
@@ -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
|
-
|
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
|
data/lib/msgpack/version.rb
CHANGED
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
|
25
|
+
unpacker = DefaultFactory.unpacker param
|
27
26
|
unpacker.feed_reference src
|
28
27
|
else
|
29
|
-
unpacker = DefaultFactory.unpacker src, param
|
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,
|
40
|
-
packer = DefaultFactory.packer(
|
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'
|
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/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
|
-
|
276
|
-
|
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(
|
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(
|
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(
|
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(
|
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
|
-
|
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
|
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)
|