image_optim 0.22.1 → 0.23.0
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 +8 -8
- data/.appveyor.yml +95 -0
- data/.rubocop.yml +3 -0
- data/.travis.yml +27 -22
- data/CHANGELOG.markdown +10 -0
- data/CONTRIBUTING.markdown +2 -1
- data/Gemfile +1 -1
- data/README.markdown +10 -2
- data/image_optim.gemspec +4 -4
- data/lib/image_optim.rb +32 -16
- data/lib/image_optim/bin_resolver/bin.rb +11 -4
- data/lib/image_optim/cache.rb +71 -0
- data/lib/image_optim/cache_path.rb +16 -0
- data/lib/image_optim/config.rb +12 -2
- data/lib/image_optim/handler.rb +1 -1
- data/lib/image_optim/image_meta.rb +5 -10
- data/lib/image_optim/optimized_path.rb +25 -0
- data/lib/image_optim/path.rb +70 -0
- data/lib/image_optim/runner/option_parser.rb +13 -0
- data/lib/image_optim/worker.rb +5 -8
- data/lib/image_optim/worker/class_methods.rb +3 -1
- data/lib/image_optim/worker/jpegoptim.rb +3 -0
- data/lib/image_optim/worker/jpegrecompress.rb +3 -0
- data/lib/image_optim/worker/pngquant.rb +3 -0
- data/script/worker_analysis +10 -9
- data/spec/image_optim/bin_resolver/comparable_condition_spec.rb +1 -1
- data/spec/image_optim/bin_resolver/simple_version_spec.rb +48 -40
- data/spec/image_optim/bin_resolver_spec.rb +190 -172
- data/spec/image_optim/cache_path_spec.rb +59 -0
- data/spec/image_optim/cache_spec.rb +159 -0
- data/spec/image_optim/cmd_spec.rb +11 -7
- data/spec/image_optim/config_spec.rb +92 -71
- data/spec/image_optim/handler_spec.rb +3 -6
- data/spec/image_optim/image_meta_spec.rb +61 -0
- data/spec/image_optim/optimized_path_spec.rb +58 -0
- data/spec/image_optim/option_helpers_spec.rb +25 -0
- data/spec/image_optim/path_spec.rb +105 -0
- data/spec/image_optim/railtie_spec.rb +6 -6
- data/spec/image_optim/runner/glob_helpers_spec.rb +2 -6
- data/spec/image_optim/runner/option_parser_spec.rb +3 -3
- data/spec/image_optim/space_spec.rb +16 -18
- data/spec/image_optim/worker/optipng_spec.rb +3 -3
- data/spec/image_optim/worker/pngquant_spec.rb +47 -7
- data/spec/image_optim/worker_spec.rb +114 -17
- data/spec/image_optim_spec.rb +58 -69
- data/spec/images/broken_jpeg +1 -0
- data/spec/spec_helper.rb +40 -10
- metadata +30 -8
- data/lib/image_optim/image_path.rb +0 -68
- data/spec/image_optim/image_path_spec.rb +0 -54
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'image_optim/cache_path'
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
describe ImageOptim::CachePath do
|
6
|
+
include CapabilityCheckHelpers
|
7
|
+
|
8
|
+
before do
|
9
|
+
stub_const('CachePath', ImageOptim::CachePath)
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#replace' do
|
13
|
+
let(:src){ CachePath.temp_file_path }
|
14
|
+
let(:dst){ CachePath.temp_file_path }
|
15
|
+
|
16
|
+
it 'moves data to destination' do
|
17
|
+
src.write('src')
|
18
|
+
|
19
|
+
src.replace(dst)
|
20
|
+
|
21
|
+
expect(dst.read).to eq('src')
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'does not remove original file' do
|
25
|
+
src.replace(dst)
|
26
|
+
|
27
|
+
expect(src).to exist
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'preserves attributes of destination file' do
|
31
|
+
skip 'full file modes are not support' unless any_file_modes_allowed?
|
32
|
+
mode = 0o666
|
33
|
+
|
34
|
+
dst.chmod(mode)
|
35
|
+
|
36
|
+
src.replace(dst)
|
37
|
+
|
38
|
+
got = dst.stat.mode & 0o777
|
39
|
+
expect(got).to eq(mode), format('expected %04o, got %04o', mode, got)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'does not preserve mtime of destination file' do
|
43
|
+
time = src.mtime
|
44
|
+
|
45
|
+
dst.utime(time - 1000, time - 1000)
|
46
|
+
|
47
|
+
src.replace(dst)
|
48
|
+
|
49
|
+
expect(dst.mtime).to be >= time
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'changes inode of destination' do
|
53
|
+
skip 'inodes are not supported' unless inodes_supported?
|
54
|
+
expect do
|
55
|
+
src.replace(dst)
|
56
|
+
end.to change{ dst.stat.ino }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,159 @@
|
|
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(anything, tmp_file)
|
73
|
+
expect(tmp_file).to receive(:rename).with(cached)
|
74
|
+
|
75
|
+
expect(cache.fetch(original){ optimized }).to eq(cached)
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'writes an empty file to cache when file is already optimized' do
|
79
|
+
cached_s = cached.to_s
|
80
|
+
expect(FileTest).to receive(:file?).with(cached_s).and_return(false)
|
81
|
+
expect(FileTest).not_to receive(:size?).with(cached_s)
|
82
|
+
expect(FileUtils).to receive(:mv).with(anything, tmp_file)
|
83
|
+
expect(tmp_file).to receive(:rename).with(cached)
|
84
|
+
|
85
|
+
expect(cache.fetch(original){ optimized }).to eq(cached)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context 'when cached file exists (options and/or workers match)' do
|
91
|
+
describe(:fetch) do
|
92
|
+
it 'returns cached file' do
|
93
|
+
cached_s = cached.to_s
|
94
|
+
allow(FileTest).to receive(:file?).with(cached_s).and_return(true)
|
95
|
+
allow(FileTest).to receive(:size?).with(cached_s).and_return(1234)
|
96
|
+
expect(FileUtils).not_to receive(:mv)
|
97
|
+
expect(File).not_to receive(:rename)
|
98
|
+
|
99
|
+
expect(cache.fetch(original){}).to eq(cached)
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'returns nil when file is already optimized' do
|
103
|
+
cached_s = cached.to_s
|
104
|
+
allow(FileTest).to receive(:file?).with(cached_s).and_return(true)
|
105
|
+
allow(FileTest).to receive(:size?).with(cached_s).and_return(nil)
|
106
|
+
expect(FileUtils).not_to receive(:mv)
|
107
|
+
expect(File).not_to receive(:rename)
|
108
|
+
|
109
|
+
expect(cache.fetch(original){}).to eq(nil)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context 'when cache is enabled (without worker digests)' do
|
116
|
+
let(:image_optim) do
|
117
|
+
double(:image_optim,
|
118
|
+
:cache_dir => cache_dir, :cache_worker_digests => false)
|
119
|
+
end
|
120
|
+
let(:cache) do
|
121
|
+
cache = Cache.new(image_optim, {})
|
122
|
+
allow(cache).
|
123
|
+
to receive(:options_by_format).
|
124
|
+
with(original.image_format).
|
125
|
+
and_return('some options!')
|
126
|
+
allow(cache).
|
127
|
+
to receive(:bins_by_format).
|
128
|
+
with(original.image_format).
|
129
|
+
and_return('some bins!')
|
130
|
+
cache
|
131
|
+
end
|
132
|
+
let(:digest){ cache.send(:digest, original, original.image_format) }
|
133
|
+
|
134
|
+
it_behaves_like 'an enabled cache'
|
135
|
+
end
|
136
|
+
|
137
|
+
context 'when cache is enabled (with worker digests)' do
|
138
|
+
let(:image_optim) do
|
139
|
+
double(:image_optim,
|
140
|
+
:cache_dir => cache_dir,
|
141
|
+
:cache_worker_digests => true)
|
142
|
+
end
|
143
|
+
let(:cache) do
|
144
|
+
cache = Cache.new(image_optim, {})
|
145
|
+
allow(cache).
|
146
|
+
to receive(:options_by_format).
|
147
|
+
with(original.image_format).
|
148
|
+
and_return('some options!')
|
149
|
+
allow(cache).
|
150
|
+
to receive(:bins_by_format).
|
151
|
+
with(original.image_format).
|
152
|
+
and_return('some bins!')
|
153
|
+
cache
|
154
|
+
end
|
155
|
+
let(:digest){ cache.send(:digest, original, original.image_format) }
|
156
|
+
|
157
|
+
it_behaves_like 'an enabled cache'
|
158
|
+
end
|
159
|
+
end
|
@@ -2,6 +2,8 @@ require 'spec_helper'
|
|
2
2
|
require 'image_optim/cmd'
|
3
3
|
|
4
4
|
describe ImageOptim::Cmd do
|
5
|
+
include CapabilityCheckHelpers
|
6
|
+
|
5
7
|
before do
|
6
8
|
stub_const('Cmd', ImageOptim::Cmd)
|
7
9
|
end
|
@@ -12,7 +14,7 @@ describe ImageOptim::Cmd do
|
|
12
14
|
end
|
13
15
|
end
|
14
16
|
|
15
|
-
describe
|
17
|
+
describe '.run' do
|
16
18
|
it 'calls system and returns result' do
|
17
19
|
status = double
|
18
20
|
expect(Cmd).to receive(:system).with('cmd', 'arg').and_return(status)
|
@@ -21,24 +23,25 @@ describe ImageOptim::Cmd do
|
|
21
23
|
end
|
22
24
|
|
23
25
|
it 'returns process success status' do
|
24
|
-
expect(Cmd.run('sh -c exit
|
26
|
+
expect(Cmd.run('sh -c "exit 0"')).to eq(true)
|
25
27
|
expect($CHILD_STATUS.exitstatus).to eq(0)
|
26
28
|
|
27
|
-
expect(Cmd.run('sh -c exit
|
29
|
+
expect(Cmd.run('sh -c "exit 1"')).to eq(false)
|
28
30
|
expect($CHILD_STATUS.exitstatus).to eq(1)
|
29
31
|
|
30
|
-
expect(Cmd.run('sh -c exit
|
32
|
+
expect(Cmd.run('sh -c "exit 66"')).to eq(false)
|
31
33
|
expect($CHILD_STATUS.exitstatus).to eq(66)
|
32
34
|
end
|
33
35
|
|
34
36
|
it 'raises SignalException if process terminates after signal' do
|
37
|
+
skip 'signals are not supported' unless signals_supported?
|
35
38
|
expect_int_exception do
|
36
39
|
Cmd.run('kill -s INT $$')
|
37
40
|
end
|
38
41
|
end
|
39
42
|
end
|
40
43
|
|
41
|
-
describe
|
44
|
+
describe '.capture' do
|
42
45
|
it 'calls ` and returns result' do
|
43
46
|
output = double
|
44
47
|
expect(Cmd).to receive(:`).with('cmd arg arg+').and_return(output)
|
@@ -50,14 +53,15 @@ describe ImageOptim::Cmd do
|
|
50
53
|
expect(Cmd.capture('echo test')).to eq("test\n")
|
51
54
|
expect($CHILD_STATUS.exitstatus).to eq(0)
|
52
55
|
|
53
|
-
expect(Cmd.capture('printf more
|
56
|
+
expect(Cmd.capture('printf more && sh -c "exit 1"')).to eq('more')
|
54
57
|
expect($CHILD_STATUS.exitstatus).to eq(1)
|
55
58
|
|
56
|
-
expect(Cmd.capture('sh -c exit
|
59
|
+
expect(Cmd.capture('sh -c "exit 66"')).to eq('')
|
57
60
|
expect($CHILD_STATUS.exitstatus).to eq(66)
|
58
61
|
end
|
59
62
|
|
60
63
|
it 'raises SignalException if process terminates after signal' do
|
64
|
+
skip 'signals are not supported' unless signals_supported?
|
61
65
|
expect_int_exception do
|
62
66
|
Cmd.capture('kill -s INT $$')
|
63
67
|
end
|
@@ -6,7 +6,7 @@ describe ImageOptim::Config do
|
|
6
6
|
stub_const('IOConfig', ImageOptim::Config)
|
7
7
|
end
|
8
8
|
|
9
|
-
describe
|
9
|
+
describe '#assert_no_unused_options!' do
|
10
10
|
before do
|
11
11
|
allow(IOConfig).to receive(:read_options).and_return({})
|
12
12
|
end
|
@@ -24,7 +24,7 @@ describe ImageOptim::Config do
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
describe
|
27
|
+
describe '#nice' do
|
28
28
|
before do
|
29
29
|
allow(IOConfig).to receive(:read_options).and_return({})
|
30
30
|
end
|
@@ -45,7 +45,7 @@ describe ImageOptim::Config do
|
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
|
-
describe
|
48
|
+
describe '#threads' do
|
49
49
|
before do
|
50
50
|
allow(IOConfig).to receive(:read_options).and_return({})
|
51
51
|
end
|
@@ -67,17 +67,40 @@ describe ImageOptim::Config do
|
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
70
|
-
describe
|
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 '#for_worker' do
|
71
98
|
before do
|
72
99
|
allow(IOConfig).to receive(:read_options).and_return({})
|
73
100
|
stub_const('Abc', Class.new do
|
74
101
|
def self.bin_sym
|
75
102
|
:abc
|
76
103
|
end
|
77
|
-
|
78
|
-
def image_formats
|
79
|
-
[]
|
80
|
-
end
|
81
104
|
end)
|
82
105
|
end
|
83
106
|
|
@@ -104,7 +127,7 @@ describe ImageOptim::Config do
|
|
104
127
|
end
|
105
128
|
end
|
106
129
|
|
107
|
-
describe '
|
130
|
+
describe '#initialize' do
|
108
131
|
it 'reads options from default locations' do
|
109
132
|
expect(IOConfig).to receive(:read_options).
|
110
133
|
with(IOConfig::GLOBAL_PATH).and_return(:a => 1, :b => 2, :c => 3)
|
@@ -150,68 +173,66 @@ describe ImageOptim::Config do
|
|
150
173
|
end
|
151
174
|
end
|
152
175
|
|
153
|
-
describe '
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
expect(IOConfig.read_options(path)).to eq({})
|
214
|
-
end
|
176
|
+
describe '.read_options' do
|
177
|
+
let(:path){ double(:path) }
|
178
|
+
let(:full_path){ double(:full_path) }
|
179
|
+
|
180
|
+
it 'warns if expand path fails' do
|
181
|
+
expect(IOConfig).to receive(:warn)
|
182
|
+
expect(File).to receive(:expand_path).
|
183
|
+
with(path).and_raise(ArgumentError)
|
184
|
+
expect(File).not_to receive(:size?)
|
185
|
+
|
186
|
+
expect(IOConfig.read_options(path)).to eq({})
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'returns empty hash if path is not a file or is an empty file' do
|
190
|
+
expect(IOConfig).not_to receive(:warn)
|
191
|
+
expect(File).to receive(:expand_path).
|
192
|
+
with(path).and_return(full_path)
|
193
|
+
expect(File).to receive(:size?).
|
194
|
+
with(full_path).and_return(false)
|
195
|
+
|
196
|
+
expect(IOConfig.read_options(path)).to eq({})
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'returns hash with deep symbolised keys from reader' do
|
200
|
+
stringified = {'config' => {'this' => true}}
|
201
|
+
symbolized = {:config => {:this => true}}
|
202
|
+
|
203
|
+
expect(IOConfig).not_to receive(:warn)
|
204
|
+
expect(File).to receive(:expand_path).
|
205
|
+
with(path).and_return(full_path)
|
206
|
+
expect(File).to receive(:size?).
|
207
|
+
with(full_path).and_return(true)
|
208
|
+
expect(YAML).to receive(:load_file).
|
209
|
+
with(full_path).and_return(stringified)
|
210
|
+
|
211
|
+
expect(IOConfig.read_options(path)).to eq(symbolized)
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'warns and returns an empty hash if reader returns non hash' do
|
215
|
+
expect(IOConfig).to receive(:warn)
|
216
|
+
expect(File).to receive(:expand_path).
|
217
|
+
with(path).and_return(full_path)
|
218
|
+
expect(File).to receive(:size?).
|
219
|
+
with(full_path).and_return(true)
|
220
|
+
expect(YAML).to receive(:load_file).
|
221
|
+
with(full_path).and_return([:config])
|
222
|
+
|
223
|
+
expect(IOConfig.read_options(path)).to eq({})
|
224
|
+
end
|
225
|
+
|
226
|
+
it 'warns and returns an empty hash if reader raises exception' do
|
227
|
+
expect(IOConfig).to receive(:warn)
|
228
|
+
expect(File).to receive(:expand_path).
|
229
|
+
with(path).and_return(full_path)
|
230
|
+
expect(File).to receive(:size?).
|
231
|
+
with(full_path).and_return(true)
|
232
|
+
expect(YAML).to receive(:load_file).
|
233
|
+
with(full_path).and_raise
|
234
|
+
|
235
|
+
expect(IOConfig.read_options(path)).to eq({})
|
215
236
|
end
|
216
237
|
end
|
217
238
|
end
|