format_parser 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe FormatParser::AIFFParser do
4
+ it 'parses an AIFF sample file' do
5
+ parse_result = subject.information_from_io(File.open(__dir__ + '/fixtures/AIFF/fixture.aiff', 'rb'))
6
+
7
+ expect(parse_result.file_nature).to eq(:audio)
8
+ expect(parse_result.file_type).to eq(:aiff)
9
+ expect(parse_result.media_duration_frames).to eq(46433)
10
+ expect(parse_result.num_audio_channels).to eq(2)
11
+ expect(parse_result.audio_sample_rate_hz).to be_within(0.01).of(44100)
12
+ expect(parse_result.media_duration_seconds).to be_within(0.01).of(1.05)
13
+ end
14
+
15
+ it 'parses a Logic Pro created AIFF sample file having a COMT chunk before a COMM chunk' do
16
+ parse_result = subject.information_from_io(File.open(__dir__ + '/fixtures/AIFF/fixture-logic-aiff.aif', 'rb'))
17
+
18
+ expect(parse_result.file_nature).to eq(:audio)
19
+ expect(parse_result.file_type).to eq(:aiff)
20
+ expect(parse_result.media_duration_frames).to eq(302400)
21
+ expect(parse_result.num_audio_channels).to eq(2)
22
+ expect(parse_result.audio_sample_rate_hz).to be_within(0.01).of(44100)
23
+ expect(parse_result.media_duration_seconds).to be_within(0.01).of(6.85)
24
+ end
25
+ end
data/spec/care_spec.rb ADDED
@@ -0,0 +1,77 @@
1
+ require 'spec_helper'
2
+
3
+ describe Care do
4
+ describe Care::Cache do
5
+ let(:source) { StringIO.new("Hello there, this is our little caching reader") }
6
+
7
+ it 'performs correct reads at various offsets' do
8
+ cache = Care::Cache.new(3)
9
+ expect(cache.byteslice(source, 0, 3)).to eq("Hel")
10
+ expect(cache.byteslice(source, 0, 7)).to eq("Hello t")
11
+ expect(cache.byteslice(source, 1, 7)).to eq("ello th")
12
+ expect(cache.byteslice(source, 11, 8)).to eq(", this i")
13
+ expect(cache.byteslice(source, 12, 12)).to eq(" this is our")
14
+ expect(cache.byteslice(source, 120, 12)).to be_nil
15
+ end
16
+
17
+ it 'can be cleared' do
18
+ cache = Care::Cache.new(3)
19
+ expect(cache.byteslice(source, 0, 3)).to eq("Hel")
20
+ expect(cache.instance_variable_get("@pages")).not_to be_empty
21
+ cache.clear
22
+ expect(cache.instance_variable_get("@pages")).to be_empty
23
+ end
24
+
25
+ it 'fits all the reads into one if the input fits into one page' do
26
+ expect(source).to receive(:read).at_most(2).times.and_call_original
27
+
28
+ cache = Care::Cache.new(source.size)
29
+
30
+ cache.byteslice(source, 0, 3)
31
+ cache.byteslice(source, 11, 8)
32
+ cache.byteslice(source, 190, 12)
33
+ cache.byteslice(source, 290, 1)
34
+ cache.byteslice(source, 890, 1)
35
+ end
36
+
37
+ it 'permits oversized reads' do
38
+ cache = Care::Cache.new(4)
39
+ expect(cache.byteslice(source, 0, 999)).to eq(source.string)
40
+ end
41
+
42
+ it 'returns nil with an empty input' do
43
+ cache = Care::Cache.new(4)
44
+ expect(cache.byteslice(StringIO.new(''), 0, 1)).to be_nil
45
+ end
46
+ end
47
+
48
+ describe Care::IOWrapper do
49
+ it 'forwards calls to read() to the Care and adjusts internal offsets' do
50
+ fake_cache_class = Class.new do
51
+ attr_reader :recorded_calls
52
+ def byteslice(io, at, n_bytes)
53
+ @recorded_calls ||= []
54
+ @recorded_calls << [io, at, n_bytes]
55
+ # Pretend reads always succeed and return the requisite number of bytes
56
+ "x" * n_bytes
57
+ end
58
+ end
59
+
60
+ cache_double = fake_cache_class.new
61
+ io_double = double('IO')
62
+
63
+ subject = Care::IOWrapper.new(io_double, cache_double)
64
+
65
+ subject.read(2)
66
+ subject.read(3)
67
+ subject.seek(11)
68
+ subject.read(5)
69
+
70
+ expect(cache_double.recorded_calls).to be_kind_of(Array)
71
+ first, second, third = *cache_double.recorded_calls
72
+ expect(first).to eq([io_double, 0, 2])
73
+ expect(second).to eq([io_double, 2, 3])
74
+ expect(third).to eq([io_double, 11, 5])
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe FormatParser::FileInformation do
4
+
5
+ context "File data checks" do
6
+ it 'succeeds with relevant attributes' do
7
+ result = described_class.new(file_nature: :image, file_type: :jpg, width_px: 42, height_px: 10, image_orientation: 1)
8
+ expect(result.file_nature).to eq(:image)
9
+ expect(result.file_type).to eq(:jpg)
10
+ expect(result.width_px).to eq(42)
11
+ expect(result.height_px).to eq(10)
12
+ expect(result.image_orientation).to eq(1)
13
+ end
14
+ end
15
+
16
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe FormatParser do
4
+ it 'returns nil when trying to parse an empty IO' do
5
+ d = StringIO.new('')
6
+ expect(FormatParser.parse(d)).to be_nil
7
+ end
8
+
9
+ it 'returns nil when parsing an IO no parser can make sense of' do
10
+ d = StringIO.new(Random.new.bytes(1))
11
+ expect(FormatParser.parse(d)).to be_nil
12
+ end
13
+
14
+ describe 'with fuzzing' do
15
+ it "returns either a valid result or a nil for all fuzzed inputs at seed #{RSpec.configuration.seed}" do
16
+ r = Random.new(RSpec.configuration.seed)
17
+ 1024.times do
18
+ random_blob = StringIO.new(r.bytes(512 * 1024))
19
+ FormatParser.parse(random_blob) # If there is an error in one of the parsers the example will raise too
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe "IOUtils" do
4
+ let(:io) {File.open(fixtures_dir + '/test.jpg', 'rb')}
5
+ include FormatParser::IOUtils
6
+
7
+ describe '#safe_read' do
8
+ it 'raises if the requested bytes are past the EOF' do
9
+ io.seek(268118) # Seek to the actual end of the file
10
+ expect {
11
+ safe_read(io, 10)
12
+ }.to raise_error(FormatParser::IOUtils::InvalidRead)
13
+ end
14
+
15
+ it 'raises if we ask for more bytes than are available' do
16
+ expect {
17
+ safe_read(io, 1_000_000)
18
+ }.to raise_error(FormatParser::IOUtils::InvalidRead)
19
+ end
20
+ end
21
+
22
+ describe '#safe_skip' do
23
+ it 'raises on a negative skip byte amount' do
24
+ fake_io = double()
25
+ expect {
26
+ safe_skip(fake_io, -5)
27
+ }.to raise_error(FormatParser::IOUtils::InvalidRead)
28
+ end
29
+
30
+ it 'uses #pos if available on the object' do
31
+ fake_io = double(pos: 11)
32
+ expect(fake_io).to receive(:seek).with(11+5)
33
+ safe_skip(fake_io, 5)
34
+ end
35
+
36
+ it 'uses #read if no #pos is available on the object' do
37
+ fake_io = double()
38
+ expect(fake_io).to receive(:read).with(5).and_return('x' * 5)
39
+ safe_skip(fake_io, 5)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe FormatParser::DPXParser do
4
+ describe 'with Depix example files' do
5
+ Dir.glob(fixtures_dir + '/dpx/*.*').each do |dpx_path|
6
+ it "is able to parse #{File.basename(dpx_path)}" do
7
+ parsed = subject.information_from_io(File.open(dpx_path, 'rb'))
8
+
9
+ expect(parsed).not_to be_nil
10
+ expect(parsed.file_nature).to eq(:image)
11
+ expect(parsed.file_type).to eq(:dpx)
12
+
13
+ # If we have an error in the struct offsets these values are likely to become
14
+ # the maximum value of a 4-byte uint, which is way higher
15
+ expect(parsed.width_px).to be_kind_of(Integer)
16
+ expect(parsed.width_px).to be_between(0, 2048)
17
+ expect(parsed.height_px).to be_kind_of(Integer)
18
+ expect(parsed.height_px).to be_between(0, 4000)
19
+ end
20
+ end
21
+
22
+ it 'correctly reads pixel dimensions' do
23
+ fi = File.open(fixtures_dir + '/dpx/026_FROM_HERO_TAPE_5-3-1_MOV.0029.dpx', 'rb')
24
+ parsed = subject.information_from_io(fi)
25
+ expect(parsed.width_px).to eq(1920)
26
+ expect(parsed.height_px).to eq(1080)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe FormatParser::EXIFParser do
4
+
5
+ # ORIENTATIONS = [
6
+ # :top_left,
7
+ # :top_right,
8
+ # :bottom_right,
9
+ # :bottom_left,
10
+ # :left_top,
11
+ # :right_top,
12
+ # :right_bottom,
13
+ # :left_bottom
14
+ # ]
15
+
16
+ describe 'is able to correctly parse orientation for all the JPEG EXIF examples from FastImage' do
17
+ Dir.glob(fixtures_dir + '/exif-orientation-testimages/jpg/*.jpg').each do |jpeg_path|
18
+ filename = File.basename(jpeg_path)
19
+ it "is able to parse #{filename}" do
20
+ parser = FormatParser::EXIFParser.new(:jpeg, File.open(jpeg_path, 'rb'))
21
+ parser.scan_image_exif
22
+ expect(parser).not_to be_nil
23
+
24
+ expect(parser.orientation).to be_kind_of(Symbol)
25
+ # Filenames in this dir correspond with the orientation of the file
26
+ expect(filename.include?(parser.orientation.to_s)).to be true
27
+ end
28
+ end
29
+ end
30
+
31
+ describe 'is able to correctly parse orientation for all the TIFF EXIF examples from FastImage' do
32
+ Dir.glob(fixtures_dir + '/exif-orientation-testimages/tiff-*/*.tif').each do |tiff_path|
33
+ filename = File.basename(tiff_path)
34
+ it "is able to parse #{filename}" do
35
+ parser = FormatParser::EXIFParser.new(:tiff, File.open(tiff_path, 'rb'))
36
+ parser.scan_image_exif
37
+ expect(parser).not_to be_nil
38
+
39
+ expect(parser.orientation).to be_kind_of(Symbol)
40
+ # Filenames in this dir correspond with the orientation of the file
41
+ expect(filename.include?(parser.orientation.to_s)).to be true
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe FormatParser::GIFParser do
4
+ describe 'is able to parse all the examples from FastImage' do
5
+ Dir.glob(fixtures_dir + '/*.gif').each do |gif_path|
6
+ it "is able to parse #{File.basename(gif_path)}" do
7
+ parsed = subject.information_from_io(File.open(gif_path, 'rb'))
8
+
9
+ expect(parsed).not_to be_nil
10
+
11
+ expect(parsed.file_nature).to eq(:image)
12
+ expect(parsed.file_type).to eq(:gif)
13
+ expect(parsed.color_mode).to eq(:indexed)
14
+
15
+ expect(parsed.width_px).to be_kind_of(Integer)
16
+ expect(parsed.width_px).to be > 0
17
+
18
+ expect(parsed.height_px).to be_kind_of(Integer)
19
+ expect(parsed.height_px).to be > 0
20
+ end
21
+ end
22
+ end
23
+
24
+ describe 'is able to correctly parse our own examples' do
25
+ it 'is able to parse the animated GIF' do
26
+ gif_path = fixtures_dir + "GIF/anim.gif"
27
+
28
+ parsed = subject.information_from_io(File.open(gif_path, 'rb'))
29
+ expect(parsed).not_to be_nil
30
+
31
+ expect(parsed.width_px).to eq(320)
32
+ expect(parsed.height_px).to eq(180)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+
3
+ describe FormatParser::JPEGParser do
4
+ describe 'is able to parse all the examples from FastImage' do
5
+ Dir.glob(fixtures_dir + '/*.jpg').each do |jpeg_path|
6
+ it "is able to parse #{File.basename(jpeg_path)}" do
7
+ parsed = subject.information_from_io(File.open(jpeg_path, 'rb'))
8
+ expect(parsed).not_to be_nil
9
+ expect(parsed.file_nature).to eq(:image)
10
+ expect(parsed.file_type).to eq(:jpg)
11
+
12
+ expect(parsed.width_px).to be_kind_of(Integer)
13
+ expect(parsed.width_px).to be > 0
14
+
15
+ expect(parsed.height_px).to be_kind_of(Integer)
16
+ expect(parsed.height_px).to be > 0
17
+ end
18
+ end
19
+ end
20
+
21
+ describe 'is able to parse all the JPEG exif examples from FastImage' do
22
+ Dir.glob(fixtures_dir + '/exif-orientation-testimages/jpg/*.jpg').each do |jpeg_path|
23
+ it "is able to parse #{File.basename(jpeg_path)}" do
24
+ parsed = subject.information_from_io(File.open(jpeg_path, 'rb'))
25
+ expect(parsed).not_to be_nil
26
+
27
+ expect(parsed.orientation).to be_kind_of(Symbol)
28
+ expect(parsed.width_px).to be > 0
29
+
30
+ expect(parsed.height_px).to be_kind_of(Integer)
31
+ expect(parsed.height_px).to be > 0
32
+ end
33
+ end
34
+ end
35
+
36
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe FormatParser::PNGParser do
4
+ describe 'is able to parse all the examples from FastImage' do
5
+ Dir.glob(fixtures_dir + '/*.png').each do |png_path|
6
+ it "is able to parse #{File.basename(png_path)}" do
7
+ parsed = subject.information_from_io(File.open(png_path, 'rb'))
8
+ expect(parsed).not_to be_nil
9
+ expect(parsed.file_nature).to eq(:image)
10
+ expect(parsed.file_type).to eq(:png)
11
+ expect(parsed.color_mode).to eq(:indexed)
12
+
13
+ expect(parsed.width_px).to be_kind_of(Integer)
14
+ expect(parsed.width_px).to be > 0
15
+
16
+ expect(parsed.height_px).to be_kind_of(Integer)
17
+ expect(parsed.height_px).to be > 0
18
+ end
19
+ end
20
+ end
21
+
22
+ it 'is able to parse an animated PNG' do
23
+ gif_path = fixtures_dir + "PNG/anim.png"
24
+
25
+ parsed = subject.information_from_io(File.open(gif_path, 'rb'))
26
+ expect(parsed).not_to be_nil
27
+
28
+ expect(parsed.width_px).to eq(320)
29
+ expect(parsed.height_px).to eq(180)
30
+ expect(parsed.has_multiple_frames).to eq(true)
31
+ expect(parsed.num_animation_or_video_frames).to eq(17)
32
+ end
33
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe FormatParser::PSDParser do
4
+ describe 'is able to parse all the examples from FastImage' do
5
+ Dir.glob(fixtures_dir + '/*.psd').each do |psd_path|
6
+ it "is able to parse #{File.basename(psd_path)}" do
7
+ parsed = subject.information_from_io(File.open(psd_path, 'rb'))
8
+
9
+ expect(parsed).not_to be_nil
10
+ expect(parsed.file_nature).to eq(:image)
11
+ expect(parsed.file_type).to eq(:psd)
12
+
13
+ expect(parsed.width_px).to be_kind_of(Integer)
14
+ expect(parsed.width_px).to be > 0
15
+
16
+ expect(parsed.height_px).to be_kind_of(Integer)
17
+ expect(parsed.height_px).to be > 0
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe FormatParser::TIFFParser do
4
+ describe 'is able to parse all the examples from FastImage' do
5
+ Dir.glob(fixtures_dir + '/TIFF/*.tif').each do |tiff_path|
6
+ it "is able to parse #{File.basename(tiff_path)}" do
7
+ parsed = subject.information_from_io(File.open(tiff_path, 'rb'))
8
+
9
+ expect(parsed).not_to be_nil
10
+ expect(parsed.file_nature).to eq(:image)
11
+ expect(parsed.file_type).to eq(:tif)
12
+
13
+ expect(parsed.width_px).to be_kind_of(Integer)
14
+ expect(parsed.width_px).to be > 0
15
+
16
+ expect(parsed.height_px).to be_kind_of(Integer)
17
+ expect(parsed.height_px).to be > 0
18
+ end
19
+ end
20
+ end
21
+
22
+ describe 'is able to parse all the TIFF exif examples from FastImage' do
23
+ Dir.glob(fixtures_dir + '/exif-orientation-testimages/tiff-*/*.tif').each do |tiff_path|
24
+ it "is able to parse #{File.basename(tiff_path)}" do
25
+ parsed = subject.information_from_io(File.open(tiff_path, 'rb'))
26
+ expect(parsed).not_to be_nil
27
+
28
+ expect(parsed.orientation).to be_kind_of(Symbol)
29
+ expect(parsed.width_px).to be > 0
30
+
31
+ expect(parsed.height_px).to be_kind_of(Integer)
32
+ expect(parsed.height_px).to be > 0
33
+ end
34
+ end
35
+ end
36
+
37
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe "ReadLimiter" do
4
+ let(:io) { StringIO.new(Random.new.bytes(1024)) }
5
+
6
+ it 'does not enforce any limits with default arguments' do
7
+ reader = FormatParser::ReadLimiter.new(io)
8
+ 2048.times { reader.seek(1) }
9
+ 2048.times { reader.read(4) }
10
+ end
11
+
12
+ it 'enforces the number of seeks' do
13
+ reader = FormatParser::ReadLimiter.new(io, max_seeks: 4)
14
+ 4.times { reader.seek(1) }
15
+ expect {
16
+ reader.seek(1)
17
+ }.to raise_error(/Seek budget exceeded/)
18
+ end
19
+
20
+ it 'enforces the number of reads' do
21
+ reader = FormatParser::ReadLimiter.new(io, max_reads: 4)
22
+ 4.times { reader.read(1) }
23
+ expect {
24
+ reader.read(1)
25
+ }.to raise_error(/calls exceeded \(4 max\)/)
26
+ end
27
+
28
+ it 'enforces the number of bytes read' do
29
+ reader = FormatParser::ReadLimiter.new(io, max_bytes: 512)
30
+ reader.read(512)
31
+ expect {
32
+ reader.read(1)
33
+ }.to raise_error(/bytes budget \(512\) exceeded/)
34
+ end
35
+ end