discourse_image_optim 0.24.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +7 -0
  2. data/.appveyor.yml +46 -0
  3. data/.gitignore +18 -0
  4. data/.rubocop.yml +110 -0
  5. data/.travis.yml +42 -0
  6. data/CHANGELOG.markdown +316 -0
  7. data/CONTRIBUTING.markdown +11 -0
  8. data/Gemfile +16 -0
  9. data/LICENSE.txt +20 -0
  10. data/README.markdown +358 -0
  11. data/Vagrantfile +38 -0
  12. data/bin/image_optim +28 -0
  13. data/image_optim.gemspec +34 -0
  14. data/lib/image_optim.rb +267 -0
  15. data/lib/image_optim/bin_resolver.rb +142 -0
  16. data/lib/image_optim/bin_resolver/bin.rb +115 -0
  17. data/lib/image_optim/bin_resolver/comparable_condition.rb +60 -0
  18. data/lib/image_optim/bin_resolver/error.rb +6 -0
  19. data/lib/image_optim/bin_resolver/simple_version.rb +31 -0
  20. data/lib/image_optim/cache.rb +72 -0
  21. data/lib/image_optim/cache_path.rb +16 -0
  22. data/lib/image_optim/cmd.rb +122 -0
  23. data/lib/image_optim/config.rb +219 -0
  24. data/lib/image_optim/configuration_error.rb +3 -0
  25. data/lib/image_optim/handler.rb +57 -0
  26. data/lib/image_optim/hash_helpers.rb +45 -0
  27. data/lib/image_optim/image_meta.rb +20 -0
  28. data/lib/image_optim/non_negative_integer_range.rb +11 -0
  29. data/lib/image_optim/optimized_path.rb +25 -0
  30. data/lib/image_optim/option_definition.rb +38 -0
  31. data/lib/image_optim/option_helpers.rb +17 -0
  32. data/lib/image_optim/path.rb +70 -0
  33. data/lib/image_optim/runner.rb +139 -0
  34. data/lib/image_optim/runner/glob_helpers.rb +45 -0
  35. data/lib/image_optim/runner/option_parser.rb +246 -0
  36. data/lib/image_optim/space.rb +29 -0
  37. data/lib/image_optim/true_false_nil.rb +16 -0
  38. data/lib/image_optim/worker.rb +170 -0
  39. data/lib/image_optim/worker/advpng.rb +37 -0
  40. data/lib/image_optim/worker/class_methods.rb +107 -0
  41. data/lib/image_optim/worker/gifsicle.rb +65 -0
  42. data/lib/image_optim/worker/jhead.rb +47 -0
  43. data/lib/image_optim/worker/jpegoptim.rb +63 -0
  44. data/lib/image_optim/worker/jpegrecompress.rb +49 -0
  45. data/lib/image_optim/worker/jpegtran.rb +48 -0
  46. data/lib/image_optim/worker/optipng.rb +53 -0
  47. data/lib/image_optim/worker/pngcrush.rb +56 -0
  48. data/lib/image_optim/worker/pngout.rb +40 -0
  49. data/lib/image_optim/worker/pngquant.rb +61 -0
  50. data/lib/image_optim/worker/svgo.rb +34 -0
  51. data/script/template/jquery-2.1.3.min.js +4 -0
  52. data/script/template/sortable-0.6.0.min.js +2 -0
  53. data/script/template/worker_analysis.erb +254 -0
  54. data/script/update_worker_options_in_readme +59 -0
  55. data/script/worker_analysis +589 -0
  56. data/spec/image_optim/bin_resolver/comparable_condition_spec.rb +37 -0
  57. data/spec/image_optim/bin_resolver/simple_version_spec.rb +65 -0
  58. data/spec/image_optim/bin_resolver_spec.rb +290 -0
  59. data/spec/image_optim/cache_path_spec.rb +57 -0
  60. data/spec/image_optim/cache_spec.rb +162 -0
  61. data/spec/image_optim/cmd_spec.rb +93 -0
  62. data/spec/image_optim/config_spec.rb +254 -0
  63. data/spec/image_optim/handler_spec.rb +90 -0
  64. data/spec/image_optim/hash_helpers_spec.rb +74 -0
  65. data/spec/image_optim/image_meta_spec.rb +61 -0
  66. data/spec/image_optim/optimized_path_spec.rb +58 -0
  67. data/spec/image_optim/option_definition_spec.rb +138 -0
  68. data/spec/image_optim/option_helpers_spec.rb +25 -0
  69. data/spec/image_optim/path_spec.rb +103 -0
  70. data/spec/image_optim/runner/glob_helpers_spec.rb +21 -0
  71. data/spec/image_optim/runner/option_parser_spec.rb +105 -0
  72. data/spec/image_optim/space_spec.rb +23 -0
  73. data/spec/image_optim/worker/optipng_spec.rb +102 -0
  74. data/spec/image_optim/worker/pngquant_spec.rb +67 -0
  75. data/spec/image_optim/worker_spec.rb +303 -0
  76. data/spec/image_optim_spec.rb +259 -0
  77. data/spec/images/broken_jpeg +1 -0
  78. data/spec/images/comparison.png +0 -0
  79. data/spec/images/decompressed.jpeg +0 -0
  80. data/spec/images/icecream.gif +0 -0
  81. data/spec/images/image.jpg +0 -0
  82. data/spec/images/invisiblepixels/generate +24 -0
  83. data/spec/images/invisiblepixels/image.png +0 -0
  84. data/spec/images/lena.jpg +0 -0
  85. data/spec/images/orient/0.jpg +0 -0
  86. data/spec/images/orient/1.jpg +0 -0
  87. data/spec/images/orient/2.jpg +0 -0
  88. data/spec/images/orient/3.jpg +0 -0
  89. data/spec/images/orient/4.jpg +0 -0
  90. data/spec/images/orient/5.jpg +0 -0
  91. data/spec/images/orient/6.jpg +0 -0
  92. data/spec/images/orient/7.jpg +0 -0
  93. data/spec/images/orient/8.jpg +0 -0
  94. data/spec/images/orient/generate +23 -0
  95. data/spec/images/orient/original.jpg +0 -0
  96. data/spec/images/quant/64.png +0 -0
  97. data/spec/images/quant/generate +25 -0
  98. data/spec/images/rails.png +0 -0
  99. data/spec/images/test.svg +3 -0
  100. data/spec/images/transparency1.png +0 -0
  101. data/spec/images/transparency2.png +0 -0
  102. data/spec/images/vergroessert.jpg +0 -0
  103. data/spec/spec_helper.rb +93 -0
  104. metadata +281 -0
@@ -0,0 +1,162 @@
1
+ require 'spec_helper'
2
+ require 'fspath'
3
+ require 'image_optim/cache'
4
+ require 'image_optim/path'
5
+
6
+ describe ImageOptim::Cache do
7
+ before do
8
+ stub_const('Cache', ImageOptim::Cache)
9
+ stub_const('CachePath', ImageOptim::CachePath)
10
+ end
11
+
12
+ let(:tmp_file){ double('/somewhere/tmp/foo/bar', :rename => 0) }
13
+
14
+ let(:cache_dir) do
15
+ dir = '/somewhere/cache'
16
+ allow(FileUtils).to receive(:mkpath).with(Regexp.new(Regexp.escape(dir)))
17
+ allow(FileUtils).to receive(:touch)
18
+ allow(FSPath).to receive(:temp_file_path) do
19
+ tmp_file
20
+ end
21
+ FSPath.new(dir)
22
+ end
23
+
24
+ let(:original) do
25
+ original = double('/somewhere/original', :image_format => :ext)
26
+ allow(Digest::SHA1).to receive(:file).with(original) do
27
+ Digest::SHA1.new << 'some content!'
28
+ end
29
+ original
30
+ end
31
+
32
+ let(:optimized) do
33
+ double('/somewhere/optimized', :format => :ext, :basename => 'optimized')
34
+ end
35
+
36
+ let(:cached) do
37
+ cached = cache_dir / digest
38
+ allow(Digest::SHA1).to receive(:file).with(cached) do
39
+ Digest::SHA1.new << 'some optimized content!'
40
+ end
41
+ CachePath.convert(cached)
42
+ end
43
+
44
+ context 'when cache is disabled (default)' do
45
+ let(:image_optim) do
46
+ double(:image_optim, :cache_dir => nil, :cache_worker_digests => false)
47
+ end
48
+ let(:cache){ Cache.new(image_optim, double) }
49
+
50
+ describe :fetch do
51
+ it 'always return block' do
52
+ expect(cache.fetch(original){ optimized }).to be optimized
53
+ end
54
+ end
55
+
56
+ describe :fetch do
57
+ it 'does not write to disk' do
58
+ expect(FileUtils).not_to receive(:mv)
59
+ expect(FileUtils).not_to receive(:touch)
60
+ expect(cache.fetch(original){ optimized })
61
+ end
62
+ end
63
+ end
64
+
65
+ shared_examples 'an enabled cache' do
66
+ context 'when cached file does not exist' do
67
+ describe :fetch do
68
+ it 'writes to cache when file is optimizable' do
69
+ cached_s = cached.to_s
70
+ expect(FileTest).to receive(:file?).with(cached_s).and_return(false)
71
+ expect(FileTest).not_to receive(:size?).with(cached_s)
72
+ expect(FileUtils).to receive(:mv).with(optimized, tmp_file)
73
+ expect(tmp_file).to receive(:rename).with(cached)
74
+
75
+ allow(File).to receive(:umask).and_return(0o024)
76
+ expect(tmp_file).to receive(:chmod).with(0o642)
77
+
78
+ expect(cache.fetch(original){ optimized }).to eq(cached)
79
+ end
80
+
81
+ it 'writes an empty file to cache when file is already optimized' do
82
+ cached_s = cached.to_s
83
+ expect(FileTest).to receive(:file?).with(cached_s).and_return(false)
84
+ expect(FileTest).not_to receive(:size?).with(cached_s)
85
+ expect(FileUtils).not_to receive(:mv)
86
+ expect(FileUtils).to receive(:touch).with(cached)
87
+
88
+ expect(cache.fetch(original){ nil }).to eq(nil)
89
+ end
90
+ end
91
+ end
92
+
93
+ context 'when cached file exists (options and/or workers match)' do
94
+ describe(:fetch) do
95
+ it 'returns cached file' do
96
+ cached_s = cached.to_s
97
+ allow(FileTest).to receive(:file?).with(cached_s).and_return(true)
98
+ allow(FileTest).to receive(:size?).with(cached_s).and_return(1234)
99
+ expect(FileUtils).not_to receive(:mv)
100
+ expect(File).not_to receive(:rename)
101
+
102
+ expect(cache.fetch(original){}).to eq(cached)
103
+ end
104
+
105
+ it 'returns nil when file is already optimized' do
106
+ cached_s = cached.to_s
107
+ allow(FileTest).to receive(:file?).with(cached_s).and_return(true)
108
+ allow(FileTest).to receive(:size?).with(cached_s).and_return(nil)
109
+ expect(FileUtils).not_to receive(:mv)
110
+ expect(File).not_to receive(:rename)
111
+
112
+ expect(cache.fetch(original){ nil }).to eq(nil)
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+ context 'when cache is enabled (without worker digests)' do
119
+ let(:image_optim) do
120
+ double(:image_optim,
121
+ :cache_dir => cache_dir, :cache_worker_digests => false)
122
+ end
123
+ let(:cache) do
124
+ cache = Cache.new(image_optim, {})
125
+ allow(cache).
126
+ to receive(:options_by_format).
127
+ with(original.image_format).
128
+ and_return('some options!')
129
+ allow(cache).
130
+ to receive(:bins_by_format).
131
+ with(original.image_format).
132
+ and_return('some bins!')
133
+ cache
134
+ end
135
+ let(:digest){ cache.send(:digest, original, original.image_format) }
136
+
137
+ it_behaves_like 'an enabled cache'
138
+ end
139
+
140
+ context 'when cache is enabled (with worker digests)' do
141
+ let(:image_optim) do
142
+ double(:image_optim,
143
+ :cache_dir => cache_dir,
144
+ :cache_worker_digests => true)
145
+ end
146
+ let(:cache) do
147
+ cache = Cache.new(image_optim, {})
148
+ allow(cache).
149
+ to receive(:options_by_format).
150
+ with(original.image_format).
151
+ and_return('some options!')
152
+ allow(cache).
153
+ to receive(:bins_by_format).
154
+ with(original.image_format).
155
+ and_return('some bins!')
156
+ cache
157
+ end
158
+ let(:digest){ cache.send(:digest, original, original.image_format) }
159
+
160
+ it_behaves_like 'an enabled cache'
161
+ end
162
+ end
@@ -0,0 +1,93 @@
1
+ require 'spec_helper'
2
+ require 'image_optim/cmd'
3
+
4
+ describe ImageOptim::Cmd do
5
+ include CapabilityCheckHelpers
6
+
7
+ before do
8
+ stub_const('Cmd', ImageOptim::Cmd)
9
+ end
10
+
11
+ def expect_int_exception(&block)
12
+ expect(&block).to raise_error(SignalException) do |error|
13
+ expect(error.message.to_s).to match(/INT|#{Signal.list['INT']}/)
14
+ end
15
+ end
16
+
17
+ describe '.run' do
18
+ it 'calls system and returns result' do
19
+ status = double
20
+ expect(Cmd).to receive(:system).with('cmd', 'arg').and_return(status)
21
+ allow(Cmd).to receive(:check_status!)
22
+ expect(Cmd.run('cmd', 'arg')).to eq(status)
23
+ end
24
+
25
+ it 'returns process success status' do
26
+ expect(Cmd.run('sh -c "exit 0"')).to eq(true)
27
+ expect($CHILD_STATUS.exitstatus).to eq(0)
28
+
29
+ expect(Cmd.run('sh -c "exit 1"')).to eq(false)
30
+ expect($CHILD_STATUS.exitstatus).to eq(1)
31
+
32
+ expect(Cmd.run('sh -c "exit 66"')).to eq(false)
33
+ expect($CHILD_STATUS.exitstatus).to eq(66)
34
+ end
35
+
36
+ it 'raises SignalException if process terminates after signal' do
37
+ skip 'signals are not supported' unless signals_supported?
38
+ expect_int_exception do
39
+ Cmd.run('kill -s INT $$')
40
+ end
41
+ end
42
+ end
43
+
44
+ describe '.run_with_timeout', :if => ImageOptim::Cmd.support_timeout? do
45
+ it 'spawns a process in a thread and returns status' do
46
+ expect(Cmd.run_with_timeout(20, 'sh -c "exit 0"')).to eq(true)
47
+
48
+ [1, 66].each do |status|
49
+ expect(Cmd.run_with_timeout(20, "sh -c \"exit #{status}\"")).
50
+ to eq(false)
51
+ end
52
+ end
53
+
54
+ it 'raises Timeout::Error if process timeout' do
55
+ expect{ Cmd.run_with_timeout(0.001, 'sleep 1') }.
56
+ to raise_error(Cmd::TimeoutExceeded)
57
+ end
58
+
59
+ it 'calls system if timeout is <= zero' do
60
+ expect(Cmd.run_with_timeout(0, 'sh -c "sleep 0.001; exit 0"')).to eq(true)
61
+
62
+ expect(Cmd.run_with_timeout(-1, 'sh -c "sleep 0.001; exit 1"')).
63
+ to eq(false)
64
+ end
65
+ end
66
+
67
+ describe '.capture' do
68
+ it 'calls ` and returns result' do
69
+ output = double
70
+ expect(Cmd).to receive(:`).with('cmd arg arg+').and_return(output)
71
+ allow(Cmd).to receive(:check_status!)
72
+ expect(Cmd.capture('cmd arg arg+')).to eq(output)
73
+ end
74
+
75
+ it 'returns output' do
76
+ expect(Cmd.capture('echo test')).to eq("test\n")
77
+ expect($CHILD_STATUS.exitstatus).to eq(0)
78
+
79
+ expect(Cmd.capture('printf more && sh -c "exit 1"')).to eq('more')
80
+ expect($CHILD_STATUS.exitstatus).to eq(1)
81
+
82
+ expect(Cmd.capture('sh -c "exit 66"')).to eq('')
83
+ expect($CHILD_STATUS.exitstatus).to eq(66)
84
+ end
85
+
86
+ it 'raises SignalException if process terminates after signal' do
87
+ skip 'signals are not supported' unless signals_supported?
88
+ expect_int_exception do
89
+ Cmd.capture('kill -s INT $$')
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,254 @@
1
+ require 'spec_helper'
2
+ require 'image_optim/config'
3
+
4
+ describe ImageOptim::Config do
5
+ before do
6
+ stub_const('IOConfig', ImageOptim::Config)
7
+ end
8
+
9
+ describe '#assert_no_unused_options!' do
10
+ before do
11
+ allow(IOConfig).to receive(:read_options).and_return({})
12
+ end
13
+
14
+ it 'does not raise when no unused options' do
15
+ config = IOConfig.new({})
16
+ config.assert_no_unused_options!
17
+ end
18
+
19
+ it 'raises when there are unused options' do
20
+ config = IOConfig.new(:unused => true)
21
+ expect do
22
+ config.assert_no_unused_options!
23
+ end.to raise_error(ImageOptim::ConfigurationError)
24
+ end
25
+ end
26
+
27
+ describe '#nice' do
28
+ before do
29
+ allow(IOConfig).to receive(:read_options).and_return({})
30
+ end
31
+
32
+ it 'is 10 by default' do
33
+ config = IOConfig.new({})
34
+ expect(config.nice).to eq(10)
35
+ end
36
+
37
+ it 'is 0 if disabled' do
38
+ config = IOConfig.new(:nice => false)
39
+ expect(config.nice).to eq(0)
40
+ end
41
+
42
+ it 'converts value to number' do
43
+ config = IOConfig.new(:nice => '13')
44
+ expect(config.nice).to eq(13)
45
+ end
46
+ end
47
+
48
+ describe '#threads' do
49
+ before do
50
+ allow(IOConfig).to receive(:read_options).and_return({})
51
+ end
52
+
53
+ it 'is processor_count by default' do
54
+ config = IOConfig.new({})
55
+ allow(config).to receive(:processor_count).and_return(13)
56
+ expect(config.threads).to eq(13)
57
+ end
58
+
59
+ it 'is 1 if disabled' do
60
+ config = IOConfig.new(:threads => false)
61
+ expect(config.threads).to eq(1)
62
+ end
63
+
64
+ it 'converts value to number' do
65
+ config = IOConfig.new(:threads => '616')
66
+ expect(config.threads).to eq(616)
67
+ end
68
+ end
69
+
70
+ describe '#cache_dir' do
71
+ before do
72
+ allow(IOConfig).to receive(:read_options).and_return({})
73
+ end
74
+
75
+ it 'is nil by default' do
76
+ config = IOConfig.new({})
77
+ expect(config.cache_dir).to be nil
78
+ end
79
+
80
+ it 'is nil if set to the empty string' do
81
+ config = IOConfig.new(:cache_dir => '')
82
+ expect(config.cache_dir).to be nil
83
+ end
84
+ end
85
+
86
+ describe '#cache_worker_digests' do
87
+ before do
88
+ allow(IOConfig).to receive(:read_options).and_return({})
89
+ end
90
+
91
+ it 'is false by default' do
92
+ config = IOConfig.new({})
93
+ expect(config.cache_worker_digests).to be false
94
+ end
95
+ end
96
+
97
+ describe '#timeout' do
98
+ before do
99
+ allow(IOConfig).to receive(:read_options).and_return({})
100
+ end
101
+
102
+ it 'is 0 by default' do
103
+ config = IOConfig.new({})
104
+ expect(config.timeout).to eq(0)
105
+ end
106
+
107
+ it 'converts value to number' do
108
+ config = IOConfig.new(:timeout => '15')
109
+ expect(config.timeout).to eq(15)
110
+ end
111
+ end
112
+
113
+ describe '#for_worker' do
114
+ before do
115
+ allow(IOConfig).to receive(:read_options).and_return({})
116
+ stub_const('Abc', Class.new do
117
+ def self.bin_sym
118
+ :abc
119
+ end
120
+ end)
121
+ end
122
+
123
+ it 'returns empty hash by default' do
124
+ config = IOConfig.new({})
125
+ expect(config.for_worker(Abc)).to eq({})
126
+ end
127
+
128
+ it 'returns passed hash' do
129
+ config = IOConfig.new(:abc => {:option => true})
130
+ expect(config.for_worker(Abc)).to eq(:option => true)
131
+ end
132
+
133
+ it 'returns {:disable => true} for false' do
134
+ config = IOConfig.new(:abc => false)
135
+ expect(config.for_worker(Abc)).to eq(:disable => true)
136
+ end
137
+
138
+ it 'raises on unknown option' do
139
+ config = IOConfig.new(:abc => 13)
140
+ expect do
141
+ config.for_worker(Abc)
142
+ end.to raise_error(ImageOptim::ConfigurationError)
143
+ end
144
+ end
145
+
146
+ describe '#initialize' do
147
+ it 'reads options from default locations' do
148
+ expect(IOConfig).to receive(:read_options).
149
+ with(IOConfig::GLOBAL_PATH).and_return(:a => 1, :b => 2, :c => 3)
150
+ expect(IOConfig).to receive(:read_options).
151
+ with(IOConfig::LOCAL_PATH).and_return(:a => 10, :b => 20)
152
+
153
+ config = IOConfig.new(:a => 100)
154
+ expect(config.get!(:a)).to eq(100)
155
+ expect(config.get!(:b)).to eq(20)
156
+ expect(config.get!(:c)).to eq(3)
157
+ config.assert_no_unused_options!
158
+ end
159
+
160
+ it 'does not read options with empty config_paths' do
161
+ expect(IOConfig).not_to receive(:read_options)
162
+
163
+ config = IOConfig.new(:config_paths => [])
164
+ config.assert_no_unused_options!
165
+ end
166
+
167
+ it 'reads options from specified paths' do
168
+ expect(IOConfig).to receive(:read_options).
169
+ with('/etc/image_optim.yml').and_return(:a => 1, :b => 2, :c => 3)
170
+ expect(IOConfig).to receive(:read_options).
171
+ with('config/image_optim.yml').and_return(:a => 10, :b => 20)
172
+
173
+ config = IOConfig.new(:a => 100, :config_paths => %w[
174
+ /etc/image_optim.yml
175
+ config/image_optim.yml
176
+ ])
177
+ expect(config.get!(:a)).to eq(100)
178
+ expect(config.get!(:b)).to eq(20)
179
+ expect(config.get!(:c)).to eq(3)
180
+ config.assert_no_unused_options!
181
+ end
182
+
183
+ it 'converts config_paths to array' do
184
+ expect(IOConfig).to receive(:read_options).
185
+ with('config/image_optim.yml').and_return({})
186
+
187
+ config = IOConfig.new(:config_paths => 'config/image_optim.yml')
188
+ config.assert_no_unused_options!
189
+ end
190
+ end
191
+
192
+ describe '.read_options' do
193
+ let(:path){ double(:path) }
194
+ let(:full_path){ double(:full_path) }
195
+
196
+ it 'warns if expand path fails' do
197
+ expect(IOConfig).to receive(:warn)
198
+ expect(File).to receive(:expand_path).
199
+ with(path).and_raise(ArgumentError)
200
+ expect(File).not_to receive(:size?)
201
+
202
+ expect(IOConfig.read_options(path)).to eq({})
203
+ end
204
+
205
+ it 'returns empty hash if path is not a file or is an empty file' do
206
+ expect(IOConfig).not_to receive(:warn)
207
+ expect(File).to receive(:expand_path).
208
+ with(path).and_return(full_path)
209
+ expect(File).to receive(:size?).
210
+ with(full_path).and_return(false)
211
+
212
+ expect(IOConfig.read_options(path)).to eq({})
213
+ end
214
+
215
+ it 'returns hash with deep symbolised keys from reader' do
216
+ stringified = {'config' => {'this' => true}}
217
+ symbolized = {:config => {:this => true}}
218
+
219
+ expect(IOConfig).not_to receive(:warn)
220
+ expect(File).to receive(:expand_path).
221
+ with(path).and_return(full_path)
222
+ expect(File).to receive(:size?).
223
+ with(full_path).and_return(true)
224
+ expect(YAML).to receive(:load_file).
225
+ with(full_path).and_return(stringified)
226
+
227
+ expect(IOConfig.read_options(path)).to eq(symbolized)
228
+ end
229
+
230
+ it 'warns and returns an empty hash if reader returns non hash' do
231
+ expect(IOConfig).to receive(:warn)
232
+ expect(File).to receive(:expand_path).
233
+ with(path).and_return(full_path)
234
+ expect(File).to receive(:size?).
235
+ with(full_path).and_return(true)
236
+ expect(YAML).to receive(:load_file).
237
+ with(full_path).and_return([:config])
238
+
239
+ expect(IOConfig.read_options(path)).to eq({})
240
+ end
241
+
242
+ it 'warns and returns an empty hash if reader raises exception' do
243
+ expect(IOConfig).to receive(:warn)
244
+ expect(File).to receive(:expand_path).
245
+ with(path).and_return(full_path)
246
+ expect(File).to receive(:size?).
247
+ with(full_path).and_return(true)
248
+ expect(YAML).to receive(:load_file).
249
+ with(full_path).and_raise
250
+
251
+ expect(IOConfig.read_options(path)).to eq({})
252
+ end
253
+ end
254
+ end