zip_tricks 4.2.3 → 4.2.4
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/.gitignore +53 -0
- data/Gemfile +3 -13
- data/README.md +36 -0
- data/Rakefile +3 -48
- data/lib/zip_tricks.rb +0 -2
- data/lib/zip_tricks/block_deflate.rb +2 -0
- data/lib/zip_tricks/version.rb +3 -0
- data/lib/zip_tricks/zip_writer.rb +59 -13
- data/testing/README_TESTING.md +12 -0
- data/testing/generate_test_files.rb +86 -0
- data/testing/in/VTYL8830.jpg +0 -0
- data/{spec/zip_tricks → testing/in}/war-and-peace.txt +0 -0
- data/testing/support.rb +83 -0
- data/testing/test-report-2016-07-28.txt +156 -0
- data/testing/test-report-2016-12-12.txt +156 -0
- data/testing/test-report.txt +28 -0
- data/zip_tricks.gemspec +36 -109
- metadata +40 -65
- data/spec/spec_helper.rb +0 -91
- data/spec/zip_tricks/block_deflate_spec.rb +0 -111
- data/spec/zip_tricks/block_write_spec.rb +0 -95
- data/spec/zip_tricks/cdir_entry_with_partial_use_of_zip64_extra_fields.bin +0 -0
- data/spec/zip_tricks/file_reader_spec.rb +0 -287
- data/spec/zip_tricks/rack_body_spec.rb +0 -34
- data/spec/zip_tricks/remote_io_spec.rb +0 -125
- data/spec/zip_tricks/remote_uncap_spec.rb +0 -100
- data/spec/zip_tricks/size_estimator_spec.rb +0 -31
- data/spec/zip_tricks/stream_crc32_spec.rb +0 -38
- data/spec/zip_tricks/streamer/writable_spec.rb +0 -27
- data/spec/zip_tricks/streamer_spec.rb +0 -296
- data/spec/zip_tricks/write_and_tell_spec.rb +0 -43
- data/spec/zip_tricks/zip_writer_spec.rb +0 -419
Binary file
|
@@ -1,287 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
describe ZipTricks::FileReader do
|
3
|
-
|
4
|
-
context 'with a file without EOCD' do
|
5
|
-
it 'raises the MissingEOCD exception and refuses to read' do
|
6
|
-
f = StringIO.new
|
7
|
-
10.times { f << ('A' * 1024 ) }
|
8
|
-
f.rewind
|
9
|
-
|
10
|
-
expect {
|
11
|
-
described_class.read_zip_structure(io: f)
|
12
|
-
}.to raise_error(described_class::MissingEOCD)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
describe 'read_zip_straight_ahead' do
|
17
|
-
it 'returns all the entries it can recover' do
|
18
|
-
zipfile = StringIO.new
|
19
|
-
war_and_peace = File.read(__dir__ + '/war-and-peace.txt')
|
20
|
-
ZipTricks::Streamer.open(zipfile) do |zip|
|
21
|
-
zip.add_stored_entry filename: 'text1.txt', crc32: Zlib.crc32(war_and_peace), size: war_and_peace.bytesize
|
22
|
-
zip << war_and_peace
|
23
|
-
zip.add_stored_entry filename: 'text2.txt', crc32: Zlib.crc32(war_and_peace), size: war_and_peace.bytesize
|
24
|
-
zip << war_and_peace
|
25
|
-
zip.add_stored_entry filename: 'text3.txt', crc32: Zlib.crc32(war_and_peace), size: war_and_peace.bytesize
|
26
|
-
zip << war_and_peace
|
27
|
-
end
|
28
|
-
zipfile.rewind
|
29
|
-
|
30
|
-
recovered_entries = described_class.read_zip_straight_ahead(io: zipfile)
|
31
|
-
expect(recovered_entries.length).to eq(3)
|
32
|
-
recovered_entries.each do |entry|
|
33
|
-
expect(entry.storage_mode).to eq(0)
|
34
|
-
expect(entry.compressed_size).to eq(496006)
|
35
|
-
expect(entry.uncompressed_size).to eq(496006)
|
36
|
-
end
|
37
|
-
|
38
|
-
first, second, third = recovered_entries
|
39
|
-
expect(first.compressed_data_offset).to eq(39)
|
40
|
-
expect(second.compressed_data_offset).to eq(496084)
|
41
|
-
expect(third.compressed_data_offset).to eq(992129)
|
42
|
-
|
43
|
-
recovered_entries.each do |entry|
|
44
|
-
zipfile.seek(entry.compressed_data_offset)
|
45
|
-
expect(zipfile.read(5)).to eq(war_and_peace[0...5])
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
it 'recovers an entry that uses Zip64 extra fields' do
|
50
|
-
zipfile = StringIO.new
|
51
|
-
w = ZipTricks::ZipWriter.new
|
52
|
-
w.write_local_file_header(io: zipfile, filename: 'big.bin', compressed_size: 0xFFFFFFFFFF, uncompressed_size: 0xFFFFFFFFF,
|
53
|
-
crc32: 0, gp_flags: 0, mtime: Time.now, storage_mode: 0)
|
54
|
-
zipfile.rewind
|
55
|
-
recovered_entries = described_class.read_zip_straight_ahead(io: zipfile)
|
56
|
-
expect(recovered_entries.length).to eq(1)
|
57
|
-
entry = recovered_entries.shift
|
58
|
-
expect(entry.compressed_size).to eq(0xFFFFFFFFFF)
|
59
|
-
end
|
60
|
-
|
61
|
-
it 'raises when an entry uses a data descriptor' do
|
62
|
-
zipfile = StringIO.new
|
63
|
-
ZipTricks::Streamer.open(zipfile) do |zip|
|
64
|
-
zip.write_deflated_file('war-and-peace.txt') do |sink|
|
65
|
-
sink << File.read(__dir__ + '/war-and-peace.txt')
|
66
|
-
end
|
67
|
-
end
|
68
|
-
zipfile.rewind
|
69
|
-
|
70
|
-
expect {
|
71
|
-
described_class.read_zip_straight_ahead(io: zipfile)
|
72
|
-
}.to raise_error(described_class::UnsupportedFeature)
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
context 'with a ZIP file where the size of the central directory is recorded incorrectly, and has a wrong value' do
|
77
|
-
it 'is still able to read the entries' do
|
78
|
-
# Purposefully create a ZIP that we will damage after
|
79
|
-
zipfile = StringIO.new
|
80
|
-
tolstoy = File.read(__dir__ + '/war-and-peace.txt')
|
81
|
-
tolstoy.force_encoding(Encoding::BINARY)
|
82
|
-
|
83
|
-
# Make a one-off Writer that corrupts the size of the central directory and says there is more data
|
84
|
-
# in it than there actually is
|
85
|
-
class EvilWriter < ZipTricks::ZipWriter
|
86
|
-
def write_end_of_central_directory(**kwargs)
|
87
|
-
kwargs[:central_directory_size] = kwargs[:central_directory_size] + 64 # Pretend there has to be more data
|
88
|
-
super(**kwargs)
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
ZipTricks::Streamer.open(zipfile, writer: EvilWriter.new) do |zip|
|
93
|
-
zip.write_deflated_file('text-1.txt') { |sink| sink << tolstoy }
|
94
|
-
zip.write_deflated_file('text-2.txt') { |sink| sink << tolstoy }
|
95
|
-
end
|
96
|
-
|
97
|
-
# Find the start of the EOCD record (signature, then the information about disk numbers and the information
|
98
|
-
# about the file counts
|
99
|
-
eocd_start_marker = [0x06054b50, 0, 0, 2, 2].pack('Vvvvv')
|
100
|
-
eocd_offset = zipfile.string.index(eocd_start_marker)
|
101
|
-
expect(eocd_offset).not_to be_nil
|
102
|
-
|
103
|
-
# Seek to the offset where the central directory size is going to be (just after the string we looked for)
|
104
|
-
zipfile.seek(eocd_offset + eocd_start_marker.bytesize)
|
105
|
-
# and overwrite it.
|
106
|
-
zipfile.write([118].pack('V'))
|
107
|
-
zipfile.rewind
|
108
|
-
|
109
|
-
entries = ZipTricks::FileReader.read_zip_structure(io: zipfile)
|
110
|
-
|
111
|
-
expect(entries.length).to eq(2)
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
context 'with an end-to-end ZIP file to read' do
|
116
|
-
it 'reads and uncompresses the file written deflated with data descriptors' do
|
117
|
-
zipfile = StringIO.new
|
118
|
-
tolstoy = File.read(__dir__ + '/war-and-peace.txt')
|
119
|
-
tolstoy.force_encoding(Encoding::BINARY)
|
120
|
-
|
121
|
-
ZipTricks::Streamer.open(zipfile) do |zip|
|
122
|
-
zip.write_deflated_file('war-and-peace.txt') do |sink|
|
123
|
-
sink << tolstoy
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
entries = described_class.read_zip_structure(io: zipfile)
|
128
|
-
expect(entries.length).to eq(1)
|
129
|
-
|
130
|
-
entry = entries.first
|
131
|
-
|
132
|
-
readback = ''
|
133
|
-
reader = entry.extractor_from(zipfile)
|
134
|
-
readback << reader.extract(10) until reader.eof?
|
135
|
-
|
136
|
-
expect(readback.bytesize).to eq(tolstoy.bytesize)
|
137
|
-
expect(readback[0..10]).to eq(tolstoy[0..10])
|
138
|
-
expect(readback[-10..-1]).to eq(tolstoy[-10..-1])
|
139
|
-
end
|
140
|
-
|
141
|
-
it 'performs local file header reads by default' do
|
142
|
-
zipfile = StringIO.new
|
143
|
-
tolstoy = File.read(__dir__ + '/war-and-peace.txt')
|
144
|
-
tolstoy.force_encoding(Encoding::BINARY)
|
145
|
-
|
146
|
-
ZipTricks::Streamer.open(zipfile) do |zip|
|
147
|
-
40.times do |i|
|
148
|
-
zip.write_deflated_file('war-and-peace-%d.txt' % i) { |sink| sink << tolstoy }
|
149
|
-
end
|
150
|
-
end
|
151
|
-
zipfile.rewind
|
152
|
-
|
153
|
-
read_monitor = ReadMonitor.new(zipfile)
|
154
|
-
entries = described_class.read_zip_structure(io: read_monitor, read_local_headers: true)
|
155
|
-
expect(read_monitor.num_reads).to eq(44)
|
156
|
-
end
|
157
|
-
|
158
|
-
it 'performs local file header reads when `read_local_headers` is set to true' do
|
159
|
-
zipfile = StringIO.new
|
160
|
-
tolstoy = File.read(__dir__ + '/war-and-peace.txt')
|
161
|
-
tolstoy.force_encoding(Encoding::BINARY)
|
162
|
-
|
163
|
-
ZipTricks::Streamer.open(zipfile) do |zip|
|
164
|
-
40.times do |i|
|
165
|
-
zip.write_deflated_file('war-and-peace-%d.txt' % i) { |sink| sink << tolstoy }
|
166
|
-
end
|
167
|
-
end
|
168
|
-
zipfile.rewind
|
169
|
-
|
170
|
-
read_monitor = ReadMonitor.new(zipfile)
|
171
|
-
entries = described_class.read_zip_structure(io: read_monitor, read_local_headers: true)
|
172
|
-
expect(read_monitor.num_reads).to eq(44)
|
173
|
-
|
174
|
-
expect(entries.length).to eq(40)
|
175
|
-
entry = entries.first
|
176
|
-
expect(entry).to be_known_offset
|
177
|
-
end
|
178
|
-
|
179
|
-
it 'performs a limited number of reads when `read_local_headers` is set to false' do
|
180
|
-
zipfile = StringIO.new
|
181
|
-
tolstoy = File.read(__dir__ + '/war-and-peace.txt')
|
182
|
-
tolstoy.force_encoding(Encoding::BINARY)
|
183
|
-
|
184
|
-
ZipTricks::Streamer.open(zipfile) do |zip|
|
185
|
-
40.times do |i|
|
186
|
-
zip.write_deflated_file('war-and-peace-%d.txt' % i) { |sink| sink << tolstoy }
|
187
|
-
end
|
188
|
-
end
|
189
|
-
zipfile.rewind
|
190
|
-
read_monitor = ReadMonitor.new(zipfile)
|
191
|
-
|
192
|
-
entries = described_class.read_zip_structure(io: read_monitor, read_local_headers: false)
|
193
|
-
|
194
|
-
expect(read_monitor.num_reads).to eq(4)
|
195
|
-
expect(entries.length).to eq(40)
|
196
|
-
entry = entries.first
|
197
|
-
expect(entry).not_to be_known_offset
|
198
|
-
expect {
|
199
|
-
entry.compressed_data_offset
|
200
|
-
}.to raise_error(/read/)
|
201
|
-
end
|
202
|
-
|
203
|
-
it 'reads the file written stored with data descriptors' do
|
204
|
-
zipfile = StringIO.new
|
205
|
-
tolstoy = File.read(__dir__ + '/war-and-peace.txt')
|
206
|
-
ZipTricks::Streamer.open(zipfile) do |zip|
|
207
|
-
zip.write_stored_file('war-and-peace.txt') do |sink|
|
208
|
-
sink << tolstoy
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
|
-
entries = described_class.read_zip_structure(io: zipfile)
|
213
|
-
expect(entries.length).to eq(1)
|
214
|
-
|
215
|
-
entry = entries.first
|
216
|
-
|
217
|
-
readback = entry.extractor_from(zipfile).extract
|
218
|
-
expect(readback.bytesize).to eq(tolstoy.bytesize)
|
219
|
-
expect(readback[0..10]).to eq(tolstoy[0..10])
|
220
|
-
end
|
221
|
-
end
|
222
|
-
|
223
|
-
describe '#get_compressed_data_offset' do
|
224
|
-
it 'reads the offset for an entry having Zip64 extra fields' do
|
225
|
-
w = ZipTricks::ZipWriter.new
|
226
|
-
out = StringIO.new
|
227
|
-
out << Random.new.bytes(7656177)
|
228
|
-
w.write_local_file_header(io: out, filename: 'some file',
|
229
|
-
compressed_size: 0xFFFFFFFF + 5, uncompressed_size: 0xFFFFFFFFF, crc32: 123, gp_flags: 4,
|
230
|
-
mtime: Time.now, storage_mode: 8)
|
231
|
-
|
232
|
-
out.rewind
|
233
|
-
|
234
|
-
compressed_data_offset = subject.get_compressed_data_offset(io: out, local_file_header_offset: 7656177)
|
235
|
-
expect(compressed_data_offset).to eq(7656236)
|
236
|
-
end
|
237
|
-
|
238
|
-
it 'reads the offset for an entry having a long name' do
|
239
|
-
w = ZipTricks::ZipWriter.new
|
240
|
-
out = StringIO.new
|
241
|
-
out << Random.new.bytes(7)
|
242
|
-
w.write_local_file_header(io: out, filename: 'This is a file with a ridiculously long name.doc',
|
243
|
-
compressed_size: 10, uncompressed_size: 15, crc32: 123, gp_flags: 4,
|
244
|
-
mtime: Time.now, storage_mode: 8)
|
245
|
-
|
246
|
-
out.rewind
|
247
|
-
|
248
|
-
compressed_data_offset = subject.get_compressed_data_offset(io: out, local_file_header_offset: 7)
|
249
|
-
expect(compressed_data_offset).to eq(85)
|
250
|
-
end
|
251
|
-
end
|
252
|
-
|
253
|
-
it 'is able to latch to the EOCD location even if the signature for the EOCD record appears all over the ZIP' do
|
254
|
-
# A VERY evil ZIP file which has this signature all over
|
255
|
-
eocd_sig = [0x06054b50].pack('V')
|
256
|
-
evil_str = "#{eocd_sig} and #{eocd_sig}"
|
257
|
-
|
258
|
-
z = StringIO.new
|
259
|
-
w = ZipTricks::ZipWriter.new
|
260
|
-
w.write_local_file_header(io: z, filename: evil_str, compressed_size: evil_str.bytesize,
|
261
|
-
uncompressed_size: evil_str.bytesize, crc32: 0x06054b50, gp_flags: 0, mtime: Time.now, storage_mode: 0)
|
262
|
-
z << evil_str
|
263
|
-
where = z.tell
|
264
|
-
w.write_central_directory_file_header(io: z, local_file_header_location: 0, gp_flags: 0, storage_mode: 0,
|
265
|
-
filename: evil_str, compressed_size: evil_str.bytesize,
|
266
|
-
uncompressed_size: evil_str.bytesize, mtime: Time.now, crc32: 0x06054b50)
|
267
|
-
w.write_end_of_central_directory(io: z, start_of_central_directory_location: where,
|
268
|
-
central_directory_size: z.tell - where, num_files_in_archive: 1, comment: evil_str)
|
269
|
-
|
270
|
-
z.rewind
|
271
|
-
entries = described_class.read_zip_structure(io: z)
|
272
|
-
expect(entries.length).to eq(1)
|
273
|
-
end
|
274
|
-
|
275
|
-
it 'can handle a Zip64 central directory fields that only contains the required fields (substitutes for standard fields)' do
|
276
|
-
# In this example central directory, 2 entries contain Zip64 extra where only the local header offset is set (8 bytes each)
|
277
|
-
# This is the exceptional case where we have to poke at a private method directly
|
278
|
-
File.open(__dir__ + '/cdir_entry_with_partial_use_of_zip64_extra_fields.bin', 'rb') do |f|
|
279
|
-
reader = described_class.new
|
280
|
-
entry = reader.send(:read_cdir_entry, f)
|
281
|
-
expect(entry.local_file_header_offset).to eq(4312401349)
|
282
|
-
expect(entry.filename).to eq('Motorhead - Ace Of Spades.srt')
|
283
|
-
expect(entry.compressed_size).to eq(69121)
|
284
|
-
expect(entry.uncompressed_size).to eq(69121)
|
285
|
-
end
|
286
|
-
end
|
287
|
-
end
|
@@ -1,34 +0,0 @@
|
|
1
|
-
require_relative '../spec_helper'
|
2
|
-
|
3
|
-
describe ZipTricks::RackBody do
|
4
|
-
it 'is usable as a Rack response body, supports each() and close()' do
|
5
|
-
output_buf = Tempfile.new('output')
|
6
|
-
|
7
|
-
file_body = SecureRandom.random_bytes(1024 * 1024 + 8981)
|
8
|
-
|
9
|
-
body = described_class.new do | zip |
|
10
|
-
zip.add_stored_entry(filename: "A file", size: file_body.bytesize, crc32: Zlib.crc32(file_body))
|
11
|
-
zip << file_body
|
12
|
-
end
|
13
|
-
|
14
|
-
body.each do | some_data |
|
15
|
-
output_buf << some_data
|
16
|
-
end
|
17
|
-
body.close
|
18
|
-
|
19
|
-
output_buf.rewind
|
20
|
-
expect(output_buf.size).to eq(1057696)
|
21
|
-
|
22
|
-
per_filename = {}
|
23
|
-
Zip::File.open(output_buf.path) do |zip_file|
|
24
|
-
# Handle entries one by one
|
25
|
-
zip_file.each do |entry|
|
26
|
-
# The entry name gets returned with a binary encoding, we have to force it back.
|
27
|
-
per_filename[entry.name] = entry.get_input_stream.read
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
expect(per_filename).to have_key('A file')
|
32
|
-
expect(per_filename['A file'].bytesize).to eq(file_body.bytesize)
|
33
|
-
end
|
34
|
-
end
|
@@ -1,125 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe ZipTricks::RemoteIO do
|
4
|
-
|
5
|
-
context 'working with the fetcher object' do
|
6
|
-
it 'asks the fetcher object to obtain the object size and the actual data when reading' do
|
7
|
-
mock_fetcher = double(request_object_size: 120, request_range: 'abc')
|
8
|
-
subject = described_class.new(mock_fetcher)
|
9
|
-
expect(subject.read(3)).to eq('abc')
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
context 'when it internally addresses a remote resource' do
|
14
|
-
it 'requests the size of the resource once via #request_object_size and does neet to read if resource is empty' do
|
15
|
-
subject = described_class.new
|
16
|
-
expect(subject).to receive(:request_object_size).and_return(0)
|
17
|
-
expect(subject.read).to be_nil
|
18
|
-
end
|
19
|
-
|
20
|
-
it 'performs remote reads when repeatedly requesting the same chunk, via #request_range' do
|
21
|
-
subject = described_class.new
|
22
|
-
|
23
|
-
expect(subject).to receive(:request_object_size).and_return(120)
|
24
|
-
allow(subject).to receive(:request_range) {|range|
|
25
|
-
expect(range).to eq(5..14)
|
26
|
-
Random.new.bytes(10)
|
27
|
-
}
|
28
|
-
20.times do
|
29
|
-
subject.seek(5, IO::SEEK_SET)
|
30
|
-
subject.read(10)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
describe '#seek' do
|
36
|
-
context 'with an unsupported mode' do
|
37
|
-
it 'raises an error' do
|
38
|
-
uncap = described_class.new
|
39
|
-
expect {
|
40
|
-
uncap.seek(123, :UNSUPPORTED)
|
41
|
-
}.to raise_error(/unsupported/i)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
context 'with SEEK_SET mode' do
|
46
|
-
it 'returns the offset of 10 when asked to seek to 10' do
|
47
|
-
uncap = described_class.new
|
48
|
-
expect(uncap).to receive(:request_object_size).and_return(100)
|
49
|
-
mode = IO::SEEK_SET
|
50
|
-
expect(uncap.seek(10, mode)).to eq(0)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
describe '#read' do
|
56
|
-
before :each do
|
57
|
-
@buf = Tempfile.new('simulated-http')
|
58
|
-
@buf.binmode
|
59
|
-
5.times { @buf << Random.new.bytes(1024 * 1024 * 3) }
|
60
|
-
@buf.rewind
|
61
|
-
|
62
|
-
@subject = described_class.new
|
63
|
-
|
64
|
-
allow(@subject).to receive(:request_object_size).and_return(@buf.size)
|
65
|
-
allow(@subject).to receive(:request_range) {|range|
|
66
|
-
@buf.read[range].tap { @buf.rewind }
|
67
|
-
}
|
68
|
-
end
|
69
|
-
|
70
|
-
after :each do
|
71
|
-
if @buf
|
72
|
-
@buf.close; @buf.unlink
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
context 'without arguments' do
|
77
|
-
it 'reads the entire buffer and alters the position pointer' do
|
78
|
-
expect(@subject.tell).to eq(0)
|
79
|
-
read = @subject.read
|
80
|
-
expect(read.bytesize).to eq(@buf.size)
|
81
|
-
expect(@subject.tell).to eq(@buf.size)
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
context 'with length' do
|
86
|
-
it 'supports an unlimited number of reads of size 0 and does not perform remote fetches for them' do
|
87
|
-
expect(@subject).not_to receive(:request_range)
|
88
|
-
20.times do
|
89
|
-
data = @subject.read(0)
|
90
|
-
expect(data).to eq('')
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
it 'returns exact amount of bytes at the start of the buffer' do
|
95
|
-
bytes_read = @subject.read(10)
|
96
|
-
expect(@subject.tell).to eq(10)
|
97
|
-
@buf.seek(0)
|
98
|
-
expect(bytes_read).to eq(@buf.read(10))
|
99
|
-
end
|
100
|
-
|
101
|
-
it 'returns exact amount of bytes from the middle of the buffer' do
|
102
|
-
@subject.seek(456, IO::SEEK_SET)
|
103
|
-
|
104
|
-
bytes_read = @subject.read(10)
|
105
|
-
expect(@subject.tell).to eq(456+10)
|
106
|
-
|
107
|
-
@buf.seek(456)
|
108
|
-
expect(bytes_read).to eq(@buf.read(10))
|
109
|
-
end
|
110
|
-
|
111
|
-
it 'returns the last N bytes it can read' do
|
112
|
-
at_end = @buf.size - 4
|
113
|
-
@subject.seek(at_end, IO::SEEK_SET)
|
114
|
-
|
115
|
-
expect(@subject.tell).to eq(15728636)
|
116
|
-
bytes_read = @subject.read(10)
|
117
|
-
expect(@subject.tell).to eq(@buf.size) # Should have moved the pos pointer to the end
|
118
|
-
|
119
|
-
expect(bytes_read.bytesize).to eq(4)
|
120
|
-
|
121
|
-
expect(@subject.tell).to eq(@buf.size)
|
122
|
-
end
|
123
|
-
end
|
124
|
-
end
|
125
|
-
end
|