discourse_image_optim 0.24.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.
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