feedx 0.10.2 → 0.12.3

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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +3 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +2 -0
  5. data/.travis.yml +12 -2
  6. data/Gemfile +0 -2
  7. data/Gemfile.lock +50 -30
  8. data/Makefile +10 -5
  9. data/compression.go +18 -0
  10. data/compression_test.go +12 -0
  11. data/consumer_test.go +5 -4
  12. data/ext/parquet/decoder.go +170 -0
  13. data/ext/parquet/decoder_test.go +88 -0
  14. data/ext/parquet/go.mod +12 -0
  15. data/ext/parquet/go.sum +134 -0
  16. data/ext/parquet/parquet.go +78 -0
  17. data/ext/parquet/parquet_test.go +28 -0
  18. data/ext/parquet/reader.go +89 -0
  19. data/ext/parquet/testdata/alltypes_plain.parquet +0 -0
  20. data/ext/parquet/types.go +51 -0
  21. data/feedx.gemspec +3 -2
  22. data/feedx_test.go +8 -24
  23. data/format.go +50 -20
  24. data/format_test.go +8 -6
  25. data/go.mod +9 -11
  26. data/go.sum +76 -28
  27. data/internal/testdata/testdata.pb.go +223 -0
  28. data/internal/testdata/testdata.proto +15 -0
  29. data/lib/feedx/cache/abstract.rb +2 -2
  30. data/lib/feedx/cache/memory.rb +1 -0
  31. data/lib/feedx/compression.rb +11 -4
  32. data/lib/feedx/compression/abstract.rb +2 -2
  33. data/lib/feedx/compression/gzip.rb +14 -16
  34. data/lib/feedx/compression/none.rb +4 -4
  35. data/lib/feedx/consumer.rb +15 -9
  36. data/lib/feedx/format.rb +18 -9
  37. data/lib/feedx/format/abstract.rb +42 -13
  38. data/lib/feedx/format/json.rb +12 -8
  39. data/lib/feedx/format/parquet.rb +102 -0
  40. data/lib/feedx/format/protobuf.rb +16 -8
  41. data/lib/feedx/producer.rb +27 -22
  42. data/lib/feedx/stream.rb +36 -23
  43. data/producer_test.go +1 -2
  44. data/reader_test.go +6 -6
  45. data/spec/feedx/compression/gzip_spec.rb +2 -2
  46. data/spec/feedx/compression/none_spec.rb +2 -2
  47. data/spec/feedx/compression_spec.rb +9 -9
  48. data/spec/feedx/consumer_spec.rb +1 -1
  49. data/spec/feedx/format/abstract_spec.rb +11 -8
  50. data/spec/feedx/format/json_spec.rb +17 -16
  51. data/spec/feedx/format/parquet_spec.rb +30 -0
  52. data/spec/feedx/format/protobuf_spec.rb +12 -11
  53. data/spec/feedx/format_spec.rb +8 -8
  54. data/spec/feedx/producer_spec.rb +6 -0
  55. data/spec/feedx/stream_spec.rb +43 -6
  56. data/spec/spec_helper.rb +17 -1
  57. metadata +33 -5
@@ -14,38 +14,43 @@ module Feedx
14
14
  # @param [Hash] opts options
15
15
  # @option opts [Enumerable,ActiveRecord::Relation] :enum relation or enumerator to stream.
16
16
  # @option opts [Symbol,Class<Feedx::Format::Abstract>] :format custom formatter. Default: from file extension.
17
- # @option opts [Hash] :format_options format encode options. Default: {}.
18
17
  # @option opts [Symbol,Class<Feedx::Compression::Abstract>] :compress enable compression. Default: from file extension.
19
18
  # @option opts [Time,Proc] :last_modified the last modified time, used to determine if a push is necessary.
20
19
  # @yield A block factory to generate the relation or enumerator.
21
20
  # @yieldreturn [Enumerable,ActiveRecord::Relation] the relation or enumerator to stream.
22
- def initialize(url, **opts, &block)
23
- @enum = opts[:enum] || block
21
+ def initialize(url, last_modified: nil, format_options: {}, enum: nil, **opts, &block)
22
+ @enum = enum || block
24
23
  raise ArgumentError, "#{self.class.name}.new expects an :enum option or a block factory" unless @enum
25
24
 
26
- @stream = Feedx::Stream.new(url, **opts)
27
- @last_mod = opts[:last_modified]
28
- @fmt_opts = opts[:format_options] || {}
25
+ @url = url
26
+ @opts = opts.merge(format_options)
27
+ @last_mod = last_modified
28
+
29
+ return if format_options.empty? || (defined?(Gem::Deprecate) && Gem::Deprecate.skip)
30
+
31
+ warn "WARNING: passing format_options is deprecated; pass the options inline instead (called from #{caller(2..2).first})."
29
32
  end
30
33
 
31
34
  def perform
32
- enum = @enum.is_a?(Proc) ? @enum.call : @enum
33
- last_mod = @last_mod.is_a?(Proc) ? @last_mod.call(enum) : @last_mod
34
- local_rev = last_mod.is_a?(Integer) ? last_mod : (last_mod.to_f * 1000).floor
35
-
36
- begin
37
- metadata = @stream.blob.info.metadata
38
- remote_rev = (metadata[META_LAST_MODIFIED] || metadata[META_LAST_MODIFIED_DC]).to_i
39
- return -1 unless local_rev > remote_rev
40
- rescue BFS::FileNotFound
41
- nil
42
- end if local_rev.positive?
43
-
44
- @stream.create metadata: { META_LAST_MODIFIED => local_rev.to_s } do |fmt|
45
- iter = enum.respond_to?(:find_each) ? :find_each : :each
46
- enum.send(iter) {|rec| fmt.encode(rec, **@fmt_opts) }
35
+ Feedx::Stream.open(@url, **@opts) do |stream|
36
+ enum = @enum.is_a?(Proc) ? @enum.call : @enum
37
+ last_mod = @last_mod.is_a?(Proc) ? @last_mod.call(enum) : @last_mod
38
+ local_rev = last_mod.is_a?(Integer) ? last_mod : (last_mod.to_f * 1000).floor
39
+
40
+ begin
41
+ metadata = stream.blob.info.metadata
42
+ remote_rev = (metadata[META_LAST_MODIFIED] || metadata[META_LAST_MODIFIED_DC]).to_i
43
+ return -1 unless local_rev > remote_rev
44
+ rescue BFS::FileNotFound
45
+ nil
46
+ end if local_rev.positive?
47
+
48
+ stream.create metadata: { META_LAST_MODIFIED => local_rev.to_s } do |fmt|
49
+ iter = enum.respond_to?(:find_each) ? :find_each : :each
50
+ enum.send(iter) {|rec| fmt.encode(rec, **@opts) }
51
+ end
52
+ stream.blob.info.size
47
53
  end
48
- @stream.blob.info.size
49
54
  end
50
55
  end
51
56
  end
@@ -6,25 +6,40 @@ module Feedx
6
6
  class Stream
7
7
  attr_reader :blob
8
8
 
9
+ # Behaves like new, but accepts an optional block.
10
+ # If a block is given, streams are automatically closed after the block is yielded.
11
+ def self.open(url, **opts)
12
+ stream = new(url, **opts)
13
+ return stream unless block_given?
14
+
15
+ begin
16
+ yield stream
17
+ ensure
18
+ stream.close
19
+ end
20
+ end
21
+
9
22
  # @param [String] url the blob URL.
10
23
  # @param [Hash] opts options
11
24
  # @option opts [Symbol,Class<Feedx::Format::Abstract>] :format custom formatter. Default: from file extension.
12
25
  # @option opts [Symbol,Class<Feedx::Compression::Abstract>] :compress enable compression. Default: from file extension.
13
- def initialize(url, **opts)
26
+ def initialize(url, format: nil, compress: nil, **opts)
14
27
  @blob = BFS::Blob.new(url)
15
- @format = detect_format(opts[:format])
16
- @compress = detect_compress(opts[:compress])
28
+ @format = detect_format(format)
29
+ @compress = detect_compress(compress)
30
+ @opts = opts
31
+
32
+ BFS.defer(self, :close)
17
33
  end
18
34
 
19
35
  # Opens the remote for reading.
20
36
  # @param [Hash] opts BFS::Blob#open options
21
37
  # @yield A block over a formatted stream.
22
38
  # @yieldparam [Feedx::Format::Abstract] formatted input stream.
23
- def open(**opts)
39
+ def open(**opts, &block)
24
40
  @blob.open(**opts) do |io|
25
- @compress.reader(io) do |cio|
26
- fmt = @format.new(cio)
27
- yield fmt
41
+ @compress.reader(io, **@opts) do |cio|
42
+ @format.decoder(cio, **@opts, &block)
28
43
  end
29
44
  end
30
45
  end
@@ -33,28 +48,29 @@ module Feedx
33
48
  # @param [Hash] opts BFS::Blob#create options
34
49
  # @yield A block over a formatted stream.
35
50
  # @yieldparam [Feedx::Format::Abstract] formatted output stream.
36
- def create(**opts)
51
+ def create(**opts, &block)
37
52
  @blob.create(**opts) do |io|
38
- @compress.writer(io) do |cio|
39
- fmt = @format.new(cio)
40
- yield fmt
53
+ @compress.writer(io, **@opts) do |cio|
54
+ @format.encoder(cio, **@opts, &block)
41
55
  end
42
56
  end
43
57
  end
44
58
 
59
+ # Closes the underlying connection.
60
+ def close
61
+ @blob.close
62
+ end
63
+
45
64
  private
46
65
 
47
66
  def detect_format(val)
48
67
  case val
49
68
  when nil
50
69
  Feedx::Format.detect(@blob.path)
51
- when Class
52
- parent = Feedx::Format::Abstract
53
- raise ArgumentError, "Class #{val} must extend #{parent}" unless val < parent
54
-
55
- val
56
- else
70
+ when String, Symbol
57
71
  Feedx::Format.resolve(val)
72
+ else
73
+ Feedx::Format.validate!(val)
58
74
  end
59
75
  end
60
76
 
@@ -62,13 +78,10 @@ module Feedx
62
78
  case val
63
79
  when nil
64
80
  Feedx::Compression.detect(@blob.path)
65
- when Class
66
- parent = Feedx::Compression::Abstract
67
- raise ArgumentError, "Class #{val} must extend #{parent}" unless val < parent
68
-
69
- val
70
- else
81
+ when String, Symbol
71
82
  Feedx::Compression.resolve(val)
83
+ else
84
+ Feedx::Compression.validate!(val)
72
85
  end
73
86
  end
74
87
  end
@@ -23,8 +23,7 @@ var _ = Describe("Producer", func() {
23
23
  atomic.AddUint32(&numRuns, 1)
24
24
 
25
25
  for i := 0; i < 10; i++ {
26
- fix := fixture
27
- if err := w.Encode(&fix); err != nil {
26
+ if err := w.Encode(seed()); err != nil {
28
27
  return err
29
28
  }
30
29
  }
@@ -5,9 +5,9 @@ import (
5
5
  "io"
6
6
  "io/ioutil"
7
7
 
8
- "github.com/bsm/feedx"
9
-
10
8
  "github.com/bsm/bfs"
9
+ "github.com/bsm/feedx"
10
+ "github.com/bsm/feedx/internal/testdata"
11
11
  . "github.com/onsi/ginkgo"
12
12
  . "github.com/onsi/gomega"
13
13
  )
@@ -38,18 +38,18 @@ var _ = Describe("Reader", func() {
38
38
  })
39
39
 
40
40
  It("should decode", func() {
41
- var msgs []MockMessage
41
+ var msgs []*testdata.MockMessage
42
42
  for {
43
- var msg MockMessage
43
+ var msg testdata.MockMessage
44
44
  err := subject.Decode(&msg)
45
45
  if err == io.EOF {
46
46
  break
47
47
  }
48
48
  Expect(err).NotTo(HaveOccurred())
49
- msgs = append(msgs, msg)
49
+ msgs = append(msgs, &msg)
50
50
  }
51
51
 
52
- Expect(msgs).To(Equal([]MockMessage{fixture, fixture, fixture}))
52
+ Expect(msgs).To(ConsistOf(seed(), seed(), seed()))
53
53
  Expect(subject.NumRead()).To(Equal(3))
54
54
  })
55
55
  })
@@ -3,13 +3,13 @@ require 'spec_helper'
3
3
  RSpec.describe Feedx::Compression::Gzip do
4
4
  it 'should wrap readers/writers' do
5
5
  wio = StringIO.new
6
- described_class.writer(wio) {|w| w.write 'xyz' * 1000 }
6
+ subject.writer(wio) {|w| w.write 'xyz' * 1000 }
7
7
  expect(wio.size).to be_within(20).of(40)
8
8
  expect(wio.string.encoding).to eq(Encoding::BINARY)
9
9
 
10
10
  data = ''
11
11
  StringIO.open(wio.string) do |rio|
12
- described_class.reader(rio) {|z| data = z.read }
12
+ subject.reader(rio) {|z| data = z.read }
13
13
  end
14
14
  expect(data.size).to eq(3000)
15
15
  expect(data.encoding).to eq(Encoding.default_external)
@@ -3,12 +3,12 @@ require 'spec_helper'
3
3
  RSpec.describe Feedx::Compression::None do
4
4
  it 'should wrap readers/writers' do
5
5
  wio = StringIO.new
6
- described_class.writer(wio) {|w| w.write 'xyz' * 1000 }
6
+ subject.writer(wio) {|w| w.write 'xyz' * 1000 }
7
7
  expect(wio.size).to eq(3000)
8
8
 
9
9
  data = ''
10
10
  StringIO.open(wio.string) do |rio|
11
- described_class.reader(rio) {|z| data = z.read }
11
+ subject.reader(rio) {|z| data = z.read }
12
12
  end
13
13
  expect(data.size).to eq(3000)
14
14
  end
@@ -2,18 +2,18 @@ require 'spec_helper'
2
2
 
3
3
  RSpec.describe Feedx::Compression do
4
4
  it 'should resolve' do
5
- expect(described_class.resolve(:gzip)).to eq(described_class::Gzip)
6
- expect(described_class.resolve(:gz)).to eq(described_class::Gzip)
7
- expect(described_class.resolve(nil)).to eq(described_class::None)
5
+ expect(described_class.resolve(:gzip)).to be_instance_of(described_class::Gzip)
6
+ expect(described_class.resolve(:gz)).to be_instance_of(described_class::Gzip)
7
+ expect(described_class.resolve(nil)).to be_instance_of(described_class::None)
8
8
  expect { described_class.resolve(:txt) }.to raise_error(/invalid compression txt/)
9
9
  end
10
10
 
11
11
  it 'should detect' do
12
- expect(described_class.detect('path/to/file.jsonz')).to eq(described_class::Gzip)
13
- expect(described_class.detect('path/to/file.json.gz')).to eq(described_class::Gzip)
14
- expect(described_class.detect('path/to/file.json')).to eq(described_class::None)
15
- expect(described_class.detect('path/to/file.pbz')).to eq(described_class::Gzip)
16
- expect(described_class.detect('path/to/file.pb.gz')).to eq(described_class::Gzip)
17
- expect(described_class.detect('path/to/file.pb')).to eq(described_class::None)
12
+ expect(described_class.detect('path/to/file.jsonz')).to be_instance_of(described_class::Gzip)
13
+ expect(described_class.detect('path/to/file.json.gz')).to be_instance_of(described_class::Gzip)
14
+ expect(described_class.detect('path/to/file.json')).to be_instance_of(described_class::None)
15
+ expect(described_class.detect('path/to/file.pbz')).to be_instance_of(described_class::Gzip)
16
+ expect(described_class.detect('path/to/file.pb.gz')).to be_instance_of(described_class::Gzip)
17
+ expect(described_class.detect('path/to/file.pb')).to be_instance_of(described_class::None)
18
18
  end
19
19
  end
@@ -8,7 +8,7 @@ RSpec.describe Feedx::Consumer do
8
8
 
9
9
  it 'should reject invalid inputs' do
10
10
  expect do
11
- described_class.each('mock:///dir/file.txt', klass) {}
11
+ described_class.each('mock:///dir/file.txt', klass)
12
12
  end.to raise_error(/unable to detect format/)
13
13
  end
14
14
 
@@ -1,17 +1,20 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  RSpec.describe Feedx::Format::Abstract do
4
- subject { Feedx::Format::JSON.new(wio) }
4
+ subject { Feedx::Format::JSON.new }
5
5
  let(:wio) { StringIO.new }
6
+ let(:rio) { StringIO.open(wio.string) }
6
7
 
7
8
  it 'should decode each' do
8
- subject.encode(Feedx::TestCase::Model.new('X'))
9
- subject.encode(Feedx::TestCase::Model.new('Y'))
10
- subject.encode(Feedx::TestCase::Message.new(title: 'Z'))
11
- StringIO.open(wio.string) do |rio|
12
- fmt = subject.class.new(rio)
13
- dec = fmt.decode_each(Feedx::TestCase::Model).to_a
14
- expect(dec.map(&:title)).to eq(%w[X Y Z])
9
+ subject.encoder wio do |enc|
10
+ enc.encode(Feedx::TestCase::Model.new('X'))
11
+ enc.encode(Feedx::TestCase::Model.new('Y'))
12
+ enc.encode(Feedx::TestCase::Message.new(title: 'Z'))
13
+ end
14
+
15
+ subject.decoder rio do |dec|
16
+ acc = dec.decode_each(Feedx::TestCase::Model).to_a
17
+ expect(acc.map(&:title)).to eq(%w[X Y Z])
15
18
  end
16
19
  end
17
20
  end
@@ -1,26 +1,27 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  RSpec.describe Feedx::Format::JSON do
4
- subject { described_class.new(wio) }
5
4
  let(:wio) { StringIO.new }
5
+ let(:rio) { StringIO.open(wio.string) }
6
6
 
7
7
  it 'should encode/decode' do
8
- subject.encode(Feedx::TestCase::Model.new('X'))
9
- subject.encode(Feedx::TestCase::Model.new('Y'))
10
- subject.encode(Feedx::TestCase::Message.new(title: 'Z'))
11
- expect(wio.string.lines).to eq [
12
- %({"title":"X","updated_at":"2018-01-05 11:25:15 UTC"}\n),
13
- %({"title":"Y","updated_at":"2018-01-05 11:25:15 UTC"}\n),
14
- %({"title":"Z"}\n),
15
- ]
8
+ subject.encoder wio do |enc|
9
+ enc.encode(Feedx::TestCase::Model.new('X'))
10
+ enc.encode(Feedx::TestCase::Model.new('Y'))
11
+ enc.encode(Feedx::TestCase::Message.new(title: 'Z'))
12
+ end
13
+ expect(wio.string).to eq(<<~JSON)
14
+ {"title":"X","updated_at":"2018-01-05 11:25:15 UTC"}
15
+ {"title":"Y","updated_at":"2018-01-05 11:25:15 UTC"}
16
+ {"title":"Z"}
17
+ JSON
16
18
 
17
- StringIO.open(wio.string) do |rio|
18
- fmt = described_class.new(rio)
19
- expect(fmt.decode(Feedx::TestCase::Model)).to eq(Feedx::TestCase::Model.new('X'))
20
- expect(fmt.decode(Feedx::TestCase::Model.new('O'))).to eq(Feedx::TestCase::Model.new('Y'))
21
- expect(fmt.decode(Feedx::TestCase::Model)).to eq(Feedx::TestCase::Model.new('Z'))
22
- expect(fmt.decode(Feedx::TestCase::Model)).to be_nil
23
- expect(fmt).to be_eof
19
+ subject.decoder rio do |dec|
20
+ expect(dec.decode(Feedx::TestCase::Model)).to eq(Feedx::TestCase::Model.new('X'))
21
+ expect(dec.decode(Feedx::TestCase::Model.new('O'))).to eq(Feedx::TestCase::Model.new('Y'))
22
+ expect(dec.decode(Feedx::TestCase::Model)).to eq(Feedx::TestCase::Model.new('Z'))
23
+ expect(dec.decode(Feedx::TestCase::Model)).to be_nil
24
+ expect(dec).to be_eof
24
25
  end
25
26
  end
26
27
  end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Feedx::Format::Parquet do
4
+ let(:wio) { StringIO.new }
5
+ let(:rio) { StringIO.open(wio.string) }
6
+
7
+ let(:schema) do
8
+ Arrow::Schema.new([
9
+ Arrow::Field.new('title', :string),
10
+ Arrow::Field.new('updated_at', type: :timestamp, unit: :second),
11
+ ])
12
+ end
13
+
14
+ it 'should encode/decode' do
15
+ subject.encoder wio, schema: schema, batch_size: 2 do |enc|
16
+ enc.encode(Feedx::TestCase::Model.new('X'))
17
+ enc.encode(Feedx::TestCase::Model.new('Y'))
18
+ enc.encode(Feedx::TestCase::Model.new('Z'))
19
+ end
20
+ expect(wio.string.bytesize).to be_within(100).of(1100)
21
+
22
+ subject.decoder rio do |dec|
23
+ expect(dec.decode(Feedx::TestCase::Model)).to eq(Feedx::TestCase::Model.new('X'))
24
+ expect(dec.decode(Feedx::TestCase::Model)).to eq(Feedx::TestCase::Model.new('Y'))
25
+ expect(dec.decode(Feedx::TestCase::Model)).to eq(Feedx::TestCase::Model.new('Z'))
26
+ expect(dec.decode(Feedx::TestCase::Model)).to be_nil
27
+ expect(dec).to be_eof
28
+ end
29
+ end
30
+ end
@@ -1,22 +1,23 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  RSpec.describe Feedx::Format::Protobuf do
4
- subject { described_class.new(wio) }
5
4
  let(:wio) { StringIO.new }
5
+ let(:rio) { StringIO.open(wio.string) }
6
6
 
7
7
  it 'should encode/decode' do
8
- subject.encode(Feedx::TestCase::Model.new('X'))
9
- subject.encode(Feedx::TestCase::Model.new('Y'))
10
- subject.encode(Feedx::TestCase::Message.new(title: 'Z'))
8
+ subject.encoder wio do |enc|
9
+ enc.encode(Feedx::TestCase::Model.new('X'))
10
+ enc.encode(Feedx::TestCase::Model.new('Y'))
11
+ enc.encode(Feedx::TestCase::Message.new(title: 'Z'))
12
+ end
11
13
  expect(wio.string.bytes).to eq([3, 10, 1, 88] + [3, 10, 1, 89] + [3, 10, 1, 90])
12
14
 
13
- StringIO.open(wio.string) do |rio|
14
- fmt = described_class.new(rio)
15
- expect(fmt.decode(Feedx::TestCase::Message)).to eq(Feedx::TestCase::Message.new(title: 'X'))
16
- expect(fmt.decode(Feedx::TestCase::Message)).to eq(Feedx::TestCase::Message.new(title: 'Y'))
17
- expect(fmt.decode(Feedx::TestCase::Message)).to eq(Feedx::TestCase::Message.new(title: 'Z'))
18
- expect(fmt.decode(Feedx::TestCase::Message)).to be_nil
19
- expect(fmt).to be_eof
15
+ subject.decoder rio do |dec|
16
+ expect(dec.decode(Feedx::TestCase::Message)).to eq(Feedx::TestCase::Message.new(title: 'X'))
17
+ expect(dec.decode(Feedx::TestCase::Message)).to eq(Feedx::TestCase::Message.new(title: 'Y'))
18
+ expect(dec.decode(Feedx::TestCase::Message)).to eq(Feedx::TestCase::Message.new(title: 'Z'))
19
+ expect(dec.decode(Feedx::TestCase::Message)).to be_nil
20
+ expect(dec).to be_eof
20
21
  end
21
22
  end
22
23
  end
@@ -2,18 +2,18 @@ require 'spec_helper'
2
2
 
3
3
  RSpec.describe Feedx::Format do
4
4
  it 'should resolve' do
5
- expect(described_class.resolve(:json)).to eq(described_class::JSON)
6
- expect(described_class.resolve(:pb)).to eq(described_class::Protobuf)
5
+ expect(described_class.resolve(:json)).to be_instance_of(described_class::JSON)
6
+ expect(described_class.resolve(:pb)).to be_instance_of(described_class::Protobuf)
7
7
  expect { described_class.resolve(:txt) }.to raise_error(/invalid format txt/)
8
8
  end
9
9
 
10
10
  it 'should detect' do
11
- expect(described_class.detect('path/to/file.json')).to eq(described_class::JSON)
12
- expect(described_class.detect('path/to/file.jsonz')).to eq(described_class::JSON)
13
- expect(described_class.detect('path/to/file.json.gz')).to eq(described_class::JSON)
14
- expect(described_class.detect('path/to/file.pb')).to eq(described_class::Protobuf)
15
- expect(described_class.detect('path/to/file.pbz')).to eq(described_class::Protobuf)
16
- expect(described_class.detect('path/to/file.pb.z')).to eq(described_class::Protobuf)
11
+ expect(described_class.detect('path/to/file.json')).to be_instance_of(described_class::JSON)
12
+ expect(described_class.detect('path/to/file.jsonz')).to be_instance_of(described_class::JSON)
13
+ expect(described_class.detect('path/to/file.json.gz')).to be_instance_of(described_class::JSON)
14
+ expect(described_class.detect('path/to/file.pb')).to be_instance_of(described_class::Protobuf)
15
+ expect(described_class.detect('path/to/file.pbz')).to be_instance_of(described_class::Protobuf)
16
+ expect(described_class.detect('path/to/file.pb.z')).to be_instance_of(described_class::Protobuf)
17
17
  expect do
18
18
  described_class.detect('path/to/file.txt')
19
19
  end.to raise_error(/unable to detect format/)