bson 4.5.0 → 4.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,758 @@
1
+ require 'spec_helper'
2
+
3
+ describe BSON::ByteBuffer do
4
+
5
+ let(:buffer) do
6
+ described_class.new
7
+ end
8
+
9
+ shared_examples_for 'does not write' do
10
+ it 'raises ArgumentError' do
11
+ expect do
12
+ modified
13
+ end.to raise_error(ArgumentError)
14
+ end
15
+
16
+ it 'does not change write position' do
17
+ expect do
18
+ modified
19
+ end.to raise_error(ArgumentError)
20
+
21
+ expect(buffer.write_position).to eq(0)
22
+ end
23
+ end
24
+
25
+ describe '#put_byte' do
26
+
27
+ let(:modified) do
28
+ buffer.put_byte(BSON::Int32::BSON_TYPE)
29
+ end
30
+
31
+ it 'appends the byte to the byte buffer' do
32
+ expect(modified.to_s).to eq(BSON::Int32::BSON_TYPE.chr)
33
+ end
34
+
35
+ it 'increments the write position by 1' do
36
+ expect(modified.write_position).to eq(1)
37
+ end
38
+
39
+ context 'when it receives a numeric value' do
40
+ it 'raises the ArgumentError exception' do
41
+ expect{buffer.put_byte(1)}.to raise_error(ArgumentError)
42
+ end
43
+ end
44
+
45
+ context 'when it receives a nil value' do
46
+ it 'raises the ArgumentError exception' do
47
+ expect{buffer.put_byte(nil)}.to raise_error(ArgumentError)
48
+ end
49
+ end
50
+
51
+ context 'when given a string of length > 1' do
52
+
53
+ let(:modified) do
54
+ buffer.put_byte('xx')
55
+ end
56
+
57
+ it_behaves_like 'does not write'
58
+ end
59
+
60
+ context 'when given a string of length 0' do
61
+
62
+ let(:modified) do
63
+ buffer.put_byte('')
64
+ end
65
+
66
+ it_behaves_like 'does not write'
67
+ end
68
+
69
+ context 'when byte is not valid utf-8' do
70
+ let(:string) do
71
+ Utils.make_byte_string([254]).freeze
72
+ end
73
+
74
+ let(:modified) do
75
+ buffer.put_byte(string)
76
+ end
77
+
78
+ it 'writes the byte' do
79
+ expect(modified.to_s).to eq(string)
80
+ end
81
+ end
82
+ end
83
+
84
+ describe '#put_bytes' do
85
+
86
+ let(:modified) do
87
+ buffer.put_bytes(BSON::Int32::BSON_TYPE)
88
+ buffer
89
+ end
90
+
91
+ it 'increments the write position by 1' do
92
+ expect(modified.write_position).to eq(1)
93
+ end
94
+
95
+ context 'when it receives a numeric value' do
96
+ it 'raises the ArgumentError exception' do
97
+ expect{buffer.put_bytes(1)}.to raise_error(ArgumentError)
98
+ end
99
+ end
100
+
101
+ context 'when it receives a nil value' do
102
+ it 'raises the ArgumentError exception' do
103
+ expect{buffer.put_bytes(nil)}.to raise_error(ArgumentError)
104
+ end
105
+ end
106
+
107
+ context 'when given a string with null bytes' do
108
+ let(:byte_str) { [0, 239, 254, 0].map(&:chr).join }
109
+
110
+ let(:modified) do
111
+ buffer.put_bytes(byte_str)
112
+ end
113
+
114
+ before do
115
+ expect(buffer.write_position).to eq(0)
116
+ expect(byte_str.length).to eq(4)
117
+ end
118
+
119
+ it 'writes the string' do
120
+ expect(modified.write_position).to eq(4)
121
+ end
122
+ end
123
+
124
+ context 'when bytes are not valid utf-8' do
125
+ let(:string) do
126
+ Utils.make_byte_string([254, 0, 255]).freeze
127
+ end
128
+
129
+ let(:modified) do
130
+ buffer.put_bytes(string)
131
+ end
132
+
133
+ it 'writes the bytes' do
134
+ expect(modified.to_s).to eq(string)
135
+ end
136
+ end
137
+ end
138
+
139
+ shared_examples_for 'bson string writer' do
140
+
141
+ context 'given empty string' do
142
+ let(:modified) do
143
+ buffer.put_string('')
144
+ end
145
+
146
+ it 'writes length and null terminator' do
147
+ expect(modified.write_position).to eq(5)
148
+ end
149
+ end
150
+
151
+ context 'when string is not valid utf-8 in utf-8 encoding' do
152
+ let(:string) do
153
+ Utils.make_byte_string([254, 253, 255], 'utf-8')
154
+ end
155
+
156
+ before do
157
+ expect(string.encoding.name).to eq('UTF-8')
158
+ end
159
+
160
+ it 'raises EncodingError' do
161
+ # Precise exception classes and messages differ between MRI and JRuby
162
+ expect do
163
+ modified
164
+ end.to raise_error(EncodingError)
165
+ end
166
+ end
167
+
168
+ context 'when string is in binary encoding and cannot be encoded in utf-8' do
169
+ let(:string) do
170
+ Utils.make_byte_string([254, 253, 255], 'binary')
171
+ end
172
+
173
+ before do
174
+ expect(string.encoding.name).to eq('ASCII-8BIT')
175
+ end
176
+
177
+ it 'raises Encoding::UndefinedConversionError' do
178
+ expect do
179
+ modified
180
+ end.to raise_error(Encoding::UndefinedConversionError, /from ASCII-8BIT to UTF-8/)
181
+ end
182
+ end
183
+ end
184
+
185
+ describe '#put_string' do
186
+
187
+ let(:modified) do
188
+ buffer.put_string(string)
189
+ end
190
+
191
+ it_behaves_like 'bson string writer'
192
+
193
+ context 'when the buffer does not need to be expanded' do
194
+
195
+ context 'when the string is UTF-8' do
196
+
197
+ let!(:modified) do
198
+ buffer.put_string('testing')
199
+ end
200
+
201
+ it 'appends the string to the byte buffer' do
202
+ expect(modified.to_s).to eq("#{8.to_bson.to_s}testing#{BSON::NULL_BYTE}")
203
+ end
204
+
205
+ it 'increments the write position by length + 5' do
206
+ expect(modified.write_position).to eq(12)
207
+ end
208
+ end
209
+ end
210
+
211
+ context 'when the buffer needs to be expanded' do
212
+
213
+ let(:string) do
214
+ 300.times.inject(""){ |s, i| s << "#{i}" }
215
+ end
216
+
217
+ context 'when no bytes exist in the buffer' do
218
+
219
+ let!(:modified) do
220
+ buffer.put_string(string)
221
+ end
222
+
223
+ it 'appends the string to the byte buffer' do
224
+ expect(modified.to_s).to eq("#{(string.bytesize + 1).to_bson.to_s}#{string}#{BSON::NULL_BYTE}")
225
+ end
226
+
227
+ it 'increments the write position by length + 5' do
228
+ expect(modified.write_position).to eq(string.bytesize + 5)
229
+ end
230
+ end
231
+
232
+ context 'when bytes exist in the buffer' do
233
+
234
+ let!(:modified) do
235
+ buffer.put_int32(4).put_string(string)
236
+ end
237
+
238
+ it 'appends the string to the byte buffer' do
239
+ expect(modified.to_s).to eq(
240
+ "#{[ 4 ].pack(BSON::Int32::PACK)}#{(string.bytesize + 1).to_bson.to_s}#{string}#{BSON::NULL_BYTE}"
241
+ )
242
+ end
243
+
244
+ it 'increments the write position by length + 5' do
245
+ expect(modified.write_position).to eq(string.bytesize + 9)
246
+ end
247
+ end
248
+ end
249
+
250
+ context 'when string is in an encoding other than utf-8' do
251
+ let(:string) do
252
+ # "\xfe"
253
+ Utils.make_byte_string([254], 'iso-8859-1')
254
+ end
255
+
256
+ let(:expected) do
257
+ # \xc3\xbe == \u00fe
258
+ Utils.make_byte_string([3, 0, 0, 0, 0xc3, 0xbe, 0])
259
+ end
260
+
261
+ it 'is written as utf-8' do
262
+ expect(modified.to_s).to eq(expected)
263
+ end
264
+ end
265
+ end
266
+
267
+ describe '#put_cstring' do
268
+
269
+ let(:modified) do
270
+ buffer.put_cstring(string)
271
+ end
272
+
273
+ it_behaves_like 'bson string writer'
274
+
275
+ context 'when argument is a string' do
276
+ context 'when the string is valid' do
277
+
278
+ let!(:modified) do
279
+ buffer.put_cstring('testing')
280
+ end
281
+
282
+ it 'appends the string plus null byte to the byte buffer' do
283
+ expect(modified.to_s).to eq("testing#{BSON::NULL_BYTE}")
284
+ end
285
+
286
+ it 'increments the write position by the length + 1' do
287
+ expect(modified.write_position).to eq(8)
288
+ end
289
+
290
+ it 'mutates receiver' do
291
+ modified
292
+ expect(buffer.write_position).to eq(8)
293
+ end
294
+ end
295
+
296
+ context "when the string contains a null byte" do
297
+
298
+ let(:string) do
299
+ "test#{BSON::NULL_BYTE}ing"
300
+ end
301
+
302
+ it 'raises ArgumentError' do
303
+ expect {
304
+ modified
305
+ }.to raise_error(ArgumentError, /String .* contains null bytes/)
306
+ end
307
+ end
308
+
309
+ context 'when string is in an encoding other than utf-8' do
310
+ let(:string) do
311
+ # "\xfe"
312
+ Utils.make_byte_string([254], 'iso-8859-1')
313
+ end
314
+
315
+ let(:expected) do
316
+ # \xc3\xbe == \u00fe
317
+ # No length prefix
318
+ Utils.make_byte_string([0xc3, 0xbe, 0])
319
+ end
320
+
321
+ it 'is written as utf-8' do
322
+ expect(modified.to_s).to eq(expected)
323
+ end
324
+ end
325
+ end
326
+
327
+ context 'when argument is a symbol' do
328
+ let(:modified) do
329
+ buffer.put_cstring(:testing)
330
+ end
331
+
332
+ it 'writes' do
333
+ expect(modified.to_s).to eq("testing#{BSON::NULL_BYTE}")
334
+ end
335
+
336
+ it 'increments the write position by the length + 1' do
337
+ expect(modified.write_position).to eq(8)
338
+ end
339
+
340
+ it 'mutates receiver' do
341
+ modified
342
+ expect(buffer.write_position).to eq(8)
343
+ end
344
+
345
+ context 'when symbol includes a null byte' do
346
+ let(:modified) do
347
+ buffer.put_cstring(:"tes\x00ing")
348
+ end
349
+
350
+ it 'raises ArgumentError' do
351
+ expect {
352
+ modified
353
+ }.to raise_error(ArgumentError, /String .* contains null bytes/)
354
+ end
355
+
356
+ it 'does not change write position' do
357
+ begin
358
+ buffer.put_cstring(:"tes\x00ing")
359
+ rescue ArgumentError
360
+ end
361
+
362
+ expect(buffer.write_position).to eq(0)
363
+ end
364
+ end
365
+ end
366
+
367
+ context 'when argument is a Fixnum' do
368
+ let(:modified) do
369
+ buffer.put_cstring(1234)
370
+ end
371
+
372
+ it 'writes' do
373
+ expect(modified.to_s).to eq("1234#{BSON::NULL_BYTE}")
374
+ end
375
+
376
+ it 'increments the write position by the length + 1' do
377
+ expect(modified.write_position).to eq(5)
378
+ end
379
+ end
380
+
381
+ context 'when argument is of an unsupported type' do
382
+ let(:modified) do
383
+ buffer.put_cstring(1234.0)
384
+ end
385
+
386
+ it 'raises TypeError' do
387
+ expect do
388
+ modified
389
+ end.to raise_error(TypeError, /Invalid type for put_cstring/)
390
+ end
391
+
392
+ it 'does not change write position' do
393
+ begin
394
+ buffer.put_cstring(1234.0)
395
+ rescue TypeError
396
+ end
397
+
398
+ expect(buffer.write_position).to eq(0)
399
+ end
400
+ end
401
+ end
402
+
403
+ describe '#put_symbol' do
404
+ context 'normal symbol' do
405
+ let(:modified) do
406
+ buffer.put_symbol(:hello)
407
+ end
408
+
409
+ it 'writes the symbol as string' do
410
+ expect(modified.to_s).to eq("\x06\x00\x00\x00hello\x00")
411
+ end
412
+
413
+ it 'advances write position' do
414
+ # 4 byte length + 5 byte string + null byte
415
+ expect(modified.write_position).to eq(10)
416
+ end
417
+ end
418
+
419
+ context 'symbol with null byte' do
420
+ let(:modified) do
421
+ buffer.put_symbol(:"he\x00lo")
422
+ end
423
+
424
+ it 'writes the symbol as string' do
425
+ expect(modified.to_s).to eq("\x06\x00\x00\x00he\x00lo\x00")
426
+ end
427
+
428
+ it 'advances write position' do
429
+ # 4 byte length + 5 byte string + null byte
430
+ expect(modified.write_position).to eq(10)
431
+ end
432
+ end
433
+
434
+ context 'when symbol is not valid utf-8' do
435
+ let(:symbol) do
436
+ Utils.make_byte_string([254, 0, 255]).to_sym
437
+ end
438
+
439
+ let(:modified) do
440
+ buffer.put_symbol(symbol)
441
+ end
442
+
443
+ it 'raises EncodingError' do
444
+ # Precise exception classes and messages differ between MRI and JRuby
445
+ expect do
446
+ modified
447
+ end.to raise_error(EncodingError)
448
+ end
449
+ end
450
+ end
451
+
452
+ describe '#put_double' do
453
+
454
+ let(:modified) do
455
+ buffer.put_double(1.2332)
456
+ end
457
+
458
+ it 'appends the double to the buffer' do
459
+ expect(modified.to_s).to eq([ 1.2332 ].pack(Float::PACK))
460
+ end
461
+
462
+ it 'increments the write position by 8' do
463
+ expect(modified.write_position).to eq(8)
464
+ end
465
+
466
+ context 'when argument is an integer' do
467
+
468
+ let(:modified) do
469
+ buffer.put_double(3)
470
+ end
471
+
472
+ it 'writes a double' do
473
+ expect(modified.to_s).to eq([ 3 ].pack(Float::PACK))
474
+ end
475
+
476
+ it 'increments the write position by 8' do
477
+ expect(modified.write_position).to eq(8)
478
+ end
479
+ end
480
+
481
+ context 'when argument is a BigNum' do
482
+ let(:value) { 123456789012345678901234567890 }
483
+
484
+ let(:modified) do
485
+ buffer.put_double(value)
486
+ end
487
+
488
+ let(:actual) do
489
+ described_class.new(modified.to_s).get_double
490
+ end
491
+
492
+ it 'writes a double' do
493
+ expect(actual).to be_within(1).of(value)
494
+ end
495
+
496
+ it 'increments the write position by 8' do
497
+ expect(modified.write_position).to eq(8)
498
+ end
499
+ end
500
+
501
+ context 'when argument is a string' do
502
+
503
+ let(:modified) do
504
+ buffer.put_double("hello")
505
+ end
506
+
507
+ it 'raises TypeError' do
508
+ expect do
509
+ modified
510
+ end.to raise_error(TypeError, /no implicit conversion to float from string|ClassCastException:.*RubyString cannot be cast to.*RubyFloat/)
511
+ expect(buffer.write_position).to eq(0)
512
+ end
513
+ end
514
+ end
515
+
516
+ describe '#put_int32' do
517
+
518
+ context 'when the integer is 32 bit' do
519
+
520
+ context 'when the integer is positive' do
521
+
522
+ let!(:modified) do
523
+ buffer.put_int32(Integer::MAX_32BIT - 1)
524
+ end
525
+
526
+ let(:expected) do
527
+ [ Integer::MAX_32BIT - 1 ].pack(BSON::Int32::PACK)
528
+ end
529
+
530
+ it 'appends the int32 to the byte buffer' do
531
+ expect(modified.to_s).to eq(expected)
532
+ end
533
+
534
+ it 'increments the write position by 4' do
535
+ expect(modified.write_position).to eq(4)
536
+ end
537
+ end
538
+
539
+ context 'when the integer is negative' do
540
+
541
+ let!(:modified) do
542
+ buffer.put_int32(Integer::MIN_32BIT + 1)
543
+ end
544
+
545
+ let(:expected) do
546
+ [ Integer::MIN_32BIT + 1 ].pack(BSON::Int32::PACK)
547
+ end
548
+
549
+ it 'appends the int32 to the byte buffer' do
550
+ expect(modified.to_s).to eq(expected)
551
+ end
552
+
553
+ it 'increments the write position by 4' do
554
+ expect(modified.write_position).to eq(4)
555
+ end
556
+ end
557
+
558
+ context 'when the integer is not 32 bit' do
559
+
560
+ it 'raises an exception' do
561
+ expect {
562
+ buffer.put_int32(Integer::MAX_64BIT - 1)
563
+ }.to raise_error(RangeError)
564
+ end
565
+ end
566
+ end
567
+
568
+ context 'when argument is a float' do
569
+
570
+ let(:modified) do
571
+ buffer.put_int32(4.934)
572
+ end
573
+
574
+ let(:expected) do
575
+ [ 4 ].pack(BSON::Int32::PACK)
576
+ end
577
+
578
+ it 'appends the int32 to the byte buffer' do
579
+ expect(modified.to_s).to eq(expected)
580
+ end
581
+
582
+ it 'increments the write position by 4' do
583
+ expect(modified.write_position).to eq(4)
584
+ end
585
+ end
586
+ end
587
+
588
+ describe '#put_int64' do
589
+
590
+ context 'when the integer is 64 bit' do
591
+
592
+ context 'when the integer is positive' do
593
+
594
+ let!(:modified) do
595
+ buffer.put_int64(Integer::MAX_64BIT - 1)
596
+ end
597
+
598
+ let(:expected) do
599
+ [ Integer::MAX_64BIT - 1 ].pack(BSON::Int64::PACK)
600
+ end
601
+
602
+ it 'appends the int64 to the byte buffer' do
603
+ expect(modified.to_s).to eq(expected)
604
+ end
605
+
606
+ it 'increments the write position by 8' do
607
+ expect(modified.write_position).to eq(8)
608
+ end
609
+ end
610
+
611
+ context 'when the integer is negative' do
612
+
613
+ let!(:modified) do
614
+ buffer.put_int64(Integer::MIN_64BIT + 1)
615
+ end
616
+
617
+ let(:expected) do
618
+ [ Integer::MIN_64BIT + 1 ].pack(BSON::Int64::PACK)
619
+ end
620
+
621
+ it 'appends the int64 to the byte buffer' do
622
+ expect(modified.to_s).to eq(expected)
623
+ end
624
+
625
+ it 'increments the write position by 8' do
626
+ expect(modified.write_position).to eq(8)
627
+ end
628
+ end
629
+
630
+ context 'when the integer is larger than 64 bit' do
631
+
632
+ it 'raises an exception' do
633
+ expect {
634
+ buffer.put_int64(Integer::MAX_64BIT + 1)
635
+ }.to raise_error(RangeError)
636
+ end
637
+ end
638
+ end
639
+
640
+ context 'when integer fits in 32 bits' do
641
+ let(:modified) do
642
+ buffer.put_int64(1)
643
+ end
644
+
645
+ it 'increments the write position by 8' do
646
+ expect(modified.write_position).to eq(8)
647
+ end
648
+ end
649
+
650
+ context 'when argument is a float' do
651
+
652
+ let(:modified) do
653
+ buffer.put_int64(4.934)
654
+ end
655
+
656
+ let(:expected) do
657
+ [ 4 ].pack(BSON::Int64::PACK)
658
+ end
659
+
660
+ it 'appends the int64 to the byte buffer' do
661
+ expect(modified.to_s).to eq(expected)
662
+ end
663
+
664
+ it 'increments the write position by 8' do
665
+ expect(modified.write_position).to eq(8)
666
+ end
667
+ end
668
+ end
669
+
670
+ describe '#replace_int32' do
671
+
672
+ let(:exp_0) do
673
+ [ 0 ].pack(BSON::Int32::PACK)
674
+ end
675
+
676
+ let(:exp_first) do
677
+ [ 5 ].pack(BSON::Int32::PACK)
678
+ end
679
+
680
+ let(:exp_second) do
681
+ [ 4 ].pack(BSON::Int32::PACK)
682
+ end
683
+
684
+ let(:exp_42) do
685
+ [ 42 ].pack(BSON::Int32::PACK)
686
+ end
687
+
688
+ let(:modified) do
689
+ buffer.replace_int32(0, 5)
690
+ end
691
+
692
+ context 'when there is sufficient data in buffer' do
693
+
694
+ before do
695
+ buffer.put_int32(0).put_int32(4)
696
+ end
697
+
698
+ it 'replaces the int32 at the location' do
699
+ expect(modified.to_s).to eq("#{exp_first}#{exp_second}")
700
+ end
701
+
702
+ context 'when the position is negative' do
703
+
704
+ let(:modified) do
705
+ buffer.replace_int32(-1, 5)
706
+ end
707
+
708
+ it 'raises ArgumentError' do
709
+ expect do
710
+ modified
711
+ end.to raise_error(ArgumentError, /Position.*cannot be negative/)
712
+ end
713
+ end
714
+
715
+ context 'when the position is 4 bytes prior to write position' do
716
+
717
+ let(:modified) do
718
+ buffer.replace_int32(4, 42)
719
+ end
720
+
721
+ it 'replaces the int32 at the location' do
722
+ expect(modified.to_s).to eq("#{exp_0}#{exp_42}")
723
+ end
724
+ end
725
+
726
+ context 'when the position exceeds allowed range' do
727
+
728
+ let(:modified) do
729
+ # Buffer has 8 bytes but we can only write up to position 4
730
+ buffer.replace_int32(5, 42)
731
+ end
732
+
733
+ it 'raises ArgumentError' do
734
+ expect do
735
+ modified
736
+ end.to raise_error(ArgumentError, /Position.*is out of bounds/)
737
+ end
738
+ end
739
+ end
740
+
741
+ context 'when there is insufficient data in buffer' do
742
+
743
+ before do
744
+ buffer.put_bytes("aa")
745
+ end
746
+
747
+ let(:modified) do
748
+ buffer.replace_int32(1, 42)
749
+ end
750
+
751
+ it 'raises ArgumentError' do
752
+ expect do
753
+ modified
754
+ end.to raise_error(ArgumentError, /Buffer does not have enough data/)
755
+ end
756
+ end
757
+ end
758
+ end