image_optim 0.26.5 → 0.30.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 +4 -4
- data/.appveyor.yml +2 -0
- data/.pre-commit-hooks.yaml +9 -0
- data/.rubocop.yml +33 -14
- data/.travis.yml +17 -15
- data/CHANGELOG.markdown +26 -0
- data/CONTRIBUTING.markdown +4 -1
- data/LICENSE.txt +1 -1
- data/README.markdown +9 -3
- data/Vagrantfile +1 -1
- data/image_optim.gemspec +7 -4
- data/lib/image_optim.rb +15 -9
- data/lib/image_optim/bin_resolver/bin.rb +10 -8
- data/lib/image_optim/bin_resolver/comparable_condition.rb +1 -0
- data/lib/image_optim/cache.rb +6 -0
- data/lib/image_optim/cache_path.rb +19 -2
- data/lib/image_optim/cmd.rb +45 -6
- data/lib/image_optim/config.rb +13 -7
- data/lib/image_optim/elapsed_time.rb +26 -0
- data/lib/image_optim/errors.rb +9 -0
- data/lib/image_optim/optimized_path.rb +1 -1
- data/lib/image_optim/path.rb +29 -6
- data/lib/image_optim/runner/option_parser.rb +6 -0
- data/lib/image_optim/timer.rb +25 -0
- data/lib/image_optim/worker.rb +29 -26
- data/lib/image_optim/worker/advpng.rb +2 -2
- data/lib/image_optim/worker/class_methods.rb +2 -0
- data/lib/image_optim/worker/gifsicle.rb +3 -3
- data/lib/image_optim/worker/jhead.rb +2 -2
- data/lib/image_optim/worker/jpegoptim.rb +8 -6
- data/lib/image_optim/worker/jpegrecompress.rb +17 -2
- data/lib/image_optim/worker/jpegtran.rb +3 -3
- data/lib/image_optim/worker/optipng.rb +4 -4
- data/lib/image_optim/worker/pngcrush.rb +4 -4
- data/lib/image_optim/worker/pngout.rb +2 -2
- data/lib/image_optim/worker/pngquant.rb +3 -2
- data/lib/image_optim/worker/svgo.rb +2 -2
- data/script/update_worker_options_in_readme +1 -1
- data/script/worker_analysis +20 -19
- data/spec/image_optim/bin_resolver_spec.rb +5 -5
- data/spec/image_optim/cache_path_spec.rb +67 -28
- data/spec/image_optim/cache_spec.rb +10 -8
- data/spec/image_optim/cmd_spec.rb +58 -6
- data/spec/image_optim/config_spec.rb +36 -20
- data/spec/image_optim/elapsed_time_spec.rb +14 -0
- data/spec/image_optim/hash_helpers_spec.rb +18 -18
- data/spec/image_optim/option_definition_spec.rb +6 -6
- data/spec/image_optim/path_spec.rb +61 -26
- data/spec/image_optim/runner/option_parser_spec.rb +4 -4
- data/spec/image_optim/timer_spec.rb +32 -0
- data/spec/image_optim/worker/jpegrecompress_spec.rb +32 -0
- data/spec/image_optim/worker/optipng_spec.rb +11 -11
- data/spec/image_optim/worker/pngquant_spec.rb +5 -5
- data/spec/image_optim/worker_spec.rb +17 -17
- data/spec/image_optim_spec.rb +50 -12
- data/spec/images/quant/generate +2 -2
- data/spec/spec_helper.rb +16 -15
- metadata +36 -12
|
@@ -11,7 +11,7 @@ describe ImageOptim::Cache do
|
|
|
11
11
|
stub_const('CachePath', ImageOptim::CachePath)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
let(:tmp_file){ double('/somewhere/tmp/foo/bar', :
|
|
14
|
+
let(:tmp_file){ double('/somewhere/tmp/foo/bar', rename: 0) }
|
|
15
15
|
|
|
16
16
|
let(:cache_dir) do
|
|
17
17
|
dir = '/somewhere/cache'
|
|
@@ -24,7 +24,7 @@ describe ImageOptim::Cache do
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
let(:original) do
|
|
27
|
-
original = double('/somewhere/original', :
|
|
27
|
+
original = double('/somewhere/original', image_format: :ext)
|
|
28
28
|
allow(Digest::SHA1).to receive(:file).with(original) do
|
|
29
29
|
Digest::SHA1.new << 'some content!'
|
|
30
30
|
end
|
|
@@ -32,7 +32,7 @@ describe ImageOptim::Cache do
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
let(:optimized) do
|
|
35
|
-
double('/somewhere/optimized', :
|
|
35
|
+
double('/somewhere/optimized', format: :ext, basename: 'optimized')
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
let(:cached) do
|
|
@@ -45,7 +45,7 @@ describe ImageOptim::Cache do
|
|
|
45
45
|
|
|
46
46
|
context 'when cache is disabled (default)' do
|
|
47
47
|
let(:image_optim) do
|
|
48
|
-
double(:image_optim, :
|
|
48
|
+
double(:image_optim, cache_dir: nil, cache_worker_digests: false, timeout: nil)
|
|
49
49
|
end
|
|
50
50
|
let(:cache){ Cache.new(image_optim, double) }
|
|
51
51
|
|
|
@@ -64,6 +64,7 @@ describe ImageOptim::Cache do
|
|
|
64
64
|
end
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
+
# rubocop:disable Style/RedundantFetchBlock
|
|
67
68
|
shared_examples 'an enabled cache' do
|
|
68
69
|
context 'when cached file does not exist' do
|
|
69
70
|
describe :fetch do
|
|
@@ -101,7 +102,7 @@ describe ImageOptim::Cache do
|
|
|
101
102
|
expect(FileUtils).not_to receive(:mv)
|
|
102
103
|
expect(File).not_to receive(:rename)
|
|
103
104
|
|
|
104
|
-
expect(cache.fetch(original){}).to eq(cached)
|
|
105
|
+
expect(cache.fetch(original){ nil }).to eq(cached)
|
|
105
106
|
end
|
|
106
107
|
|
|
107
108
|
it 'returns nil when file is already optimized' do
|
|
@@ -116,11 +117,12 @@ describe ImageOptim::Cache do
|
|
|
116
117
|
end
|
|
117
118
|
end
|
|
118
119
|
end
|
|
120
|
+
# rubocop:enable Style/RedundantFetchBlock
|
|
119
121
|
|
|
120
122
|
context 'when cache is enabled (without worker digests)' do
|
|
121
123
|
let(:image_optim) do
|
|
122
124
|
double(:image_optim,
|
|
123
|
-
:cache_dir
|
|
125
|
+
cache_dir: cache_dir, cache_worker_digests: false, timeout: nil)
|
|
124
126
|
end
|
|
125
127
|
let(:cache) do
|
|
126
128
|
cache = Cache.new(image_optim, {})
|
|
@@ -142,8 +144,8 @@ describe ImageOptim::Cache do
|
|
|
142
144
|
context 'when cache is enabled (with worker digests)' do
|
|
143
145
|
let(:image_optim) do
|
|
144
146
|
double(:image_optim,
|
|
145
|
-
:
|
|
146
|
-
:
|
|
147
|
+
cache_dir: cache_dir,
|
|
148
|
+
cache_worker_digests: true, timeout: nil)
|
|
147
149
|
end
|
|
148
150
|
let(:cache) do
|
|
149
151
|
cache = Cache.new(image_optim, {})
|
|
@@ -4,8 +4,6 @@ require 'spec_helper'
|
|
|
4
4
|
require 'image_optim/cmd'
|
|
5
5
|
|
|
6
6
|
describe ImageOptim::Cmd do
|
|
7
|
-
include CapabilityCheckHelpers
|
|
8
|
-
|
|
9
7
|
before do
|
|
10
8
|
stub_const('Cmd', ImageOptim::Cmd)
|
|
11
9
|
end
|
|
@@ -35,12 +33,67 @@ describe ImageOptim::Cmd do
|
|
|
35
33
|
expect($CHILD_STATUS.exitstatus).to eq(66)
|
|
36
34
|
end
|
|
37
35
|
|
|
38
|
-
it 'raises SignalException if process terminates after signal' do
|
|
39
|
-
skip 'signals are not supported' unless signals_supported?
|
|
36
|
+
it 'raises SignalException if process terminates after signal', skip: SkipConditions[:signals_support] do
|
|
40
37
|
expect_int_exception do
|
|
41
38
|
Cmd.run('kill -s INT $$')
|
|
42
39
|
end
|
|
43
40
|
end
|
|
41
|
+
|
|
42
|
+
context 'with timeout' do
|
|
43
|
+
it 'returns process success status' do
|
|
44
|
+
expect(Cmd.run('sh -c "exit 0"', timeout: 1)).to eq(true)
|
|
45
|
+
|
|
46
|
+
expect(Cmd.run('sh -c "exit 1"', timeout: 1)).to eq(false)
|
|
47
|
+
|
|
48
|
+
expect(Cmd.run('sh -c "exit 66"', timeout: 1)).to eq(false)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'raises SignalException if process terminates after signal', skip: SkipConditions[:signals_support] do
|
|
52
|
+
expect_int_exception do
|
|
53
|
+
Cmd.run('kill -s INT $$', timeout: 1)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it 'raises TimeoutExceeded if process does not exit until timeout' do
|
|
58
|
+
expect do
|
|
59
|
+
Cmd.run('sleep 10', timeout: 0)
|
|
60
|
+
end.to raise_error(ImageOptim::Errors::TimeoutExceeded)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it 'does not leave zombie threads' do
|
|
64
|
+
expect do
|
|
65
|
+
begin
|
|
66
|
+
Cmd.run('sleep 10', timeout: 0)
|
|
67
|
+
rescue ImageOptim::Errors::TimeoutExceeded
|
|
68
|
+
# noop
|
|
69
|
+
end
|
|
70
|
+
end.not_to change{ Thread.list }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'receives TERM', skip: SkipConditions[:signals_support] do
|
|
74
|
+
waiter = double
|
|
75
|
+
allow(Process).to receive(:detach).once{ |pid| @pid = pid; waiter }
|
|
76
|
+
allow(waiter).to receive(:join){ sleep 0.1; nil }
|
|
77
|
+
|
|
78
|
+
expect do
|
|
79
|
+
Cmd.run('sleep 5', timeout: 0.1)
|
|
80
|
+
end.to raise_error(ImageOptim::Errors::TimeoutExceeded)
|
|
81
|
+
|
|
82
|
+
expect(Process.wait2(@pid).last.termsig).to eq(Signal.list['TERM'])
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it 'receives KILL if it does not react on TERM', skip: SkipConditions[:signals_support] do
|
|
86
|
+
waiter = double
|
|
87
|
+
allow(Process).to receive(:detach).once{ |pid| @pid = pid; waiter }
|
|
88
|
+
allow(waiter).to receive(:join){ sleep 0.1; nil }
|
|
89
|
+
|
|
90
|
+
expect do
|
|
91
|
+
Cmd.run('trap "" TERM; sleep 5', timeout: 0.1)
|
|
92
|
+
end.to raise_error(ImageOptim::Errors::TimeoutExceeded)
|
|
93
|
+
|
|
94
|
+
expect(Process.wait2(@pid).last.termsig).to eq(Signal.list['KILL'])
|
|
95
|
+
end
|
|
96
|
+
end
|
|
44
97
|
end
|
|
45
98
|
|
|
46
99
|
describe '.capture' do
|
|
@@ -62,8 +115,7 @@ describe ImageOptim::Cmd do
|
|
|
62
115
|
expect($CHILD_STATUS.exitstatus).to eq(66)
|
|
63
116
|
end
|
|
64
117
|
|
|
65
|
-
it 'raises SignalException if process terminates after signal' do
|
|
66
|
-
skip 'signals are not supported' unless signals_supported?
|
|
118
|
+
it 'raises SignalException if process terminates after signal', skip: SkipConditions[:signals_support] do
|
|
67
119
|
expect_int_exception do
|
|
68
120
|
Cmd.capture('kill -s INT $$')
|
|
69
121
|
end
|
|
@@ -19,7 +19,7 @@ describe ImageOptim::Config do
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
it 'raises when there are unused options' do
|
|
22
|
-
config = IOConfig.new(:
|
|
22
|
+
config = IOConfig.new(unused: true)
|
|
23
23
|
expect do
|
|
24
24
|
config.assert_no_unused_options!
|
|
25
25
|
end.to raise_error(ImageOptim::ConfigurationError)
|
|
@@ -37,12 +37,12 @@ describe ImageOptim::Config do
|
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
it 'is 0 if disabled' do
|
|
40
|
-
config = IOConfig.new(:
|
|
40
|
+
config = IOConfig.new(nice: false)
|
|
41
41
|
expect(config.nice).to eq(0)
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
it 'converts value to number' do
|
|
45
|
-
config = IOConfig.new(:
|
|
45
|
+
config = IOConfig.new(nice: '13')
|
|
46
46
|
expect(config.nice).to eq(13)
|
|
47
47
|
end
|
|
48
48
|
end
|
|
@@ -59,16 +59,32 @@ describe ImageOptim::Config do
|
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
it 'is 1 if disabled' do
|
|
62
|
-
config = IOConfig.new(:
|
|
62
|
+
config = IOConfig.new(threads: false)
|
|
63
63
|
expect(config.threads).to eq(1)
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
it 'converts value to number' do
|
|
67
|
-
config = IOConfig.new(:
|
|
67
|
+
config = IOConfig.new(threads: '616')
|
|
68
68
|
expect(config.threads).to eq(616)
|
|
69
69
|
end
|
|
70
70
|
end
|
|
71
71
|
|
|
72
|
+
describe '#timeout' do
|
|
73
|
+
before do
|
|
74
|
+
allow(IOConfig).to receive(:read_options).and_return({})
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it 'is nil by default' do
|
|
78
|
+
config = IOConfig.new({})
|
|
79
|
+
expect(config.timeout).to eq(nil)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it 'converts value to a float' do
|
|
83
|
+
config = IOConfig.new(timeout: '15.1')
|
|
84
|
+
expect(config.timeout).to eq(15.1)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
72
88
|
describe '#cache_dir' do
|
|
73
89
|
before do
|
|
74
90
|
allow(IOConfig).to receive(:read_options).and_return({})
|
|
@@ -80,7 +96,7 @@ describe ImageOptim::Config do
|
|
|
80
96
|
end
|
|
81
97
|
|
|
82
98
|
it 'is nil if set to the empty string' do
|
|
83
|
-
config = IOConfig.new(:
|
|
99
|
+
config = IOConfig.new(cache_dir: '')
|
|
84
100
|
expect(config.cache_dir).to be nil
|
|
85
101
|
end
|
|
86
102
|
end
|
|
@@ -112,17 +128,17 @@ describe ImageOptim::Config do
|
|
|
112
128
|
end
|
|
113
129
|
|
|
114
130
|
it 'returns passed hash' do
|
|
115
|
-
config = IOConfig.new(:
|
|
116
|
-
expect(config.for_worker(Abc)).to eq(:
|
|
131
|
+
config = IOConfig.new(abc: {option: true})
|
|
132
|
+
expect(config.for_worker(Abc)).to eq(option: true)
|
|
117
133
|
end
|
|
118
134
|
|
|
119
135
|
it 'returns {:disable => true} for false' do
|
|
120
|
-
config = IOConfig.new(:
|
|
121
|
-
expect(config.for_worker(Abc)).to eq(:
|
|
136
|
+
config = IOConfig.new(abc: false)
|
|
137
|
+
expect(config.for_worker(Abc)).to eq(disable: true)
|
|
122
138
|
end
|
|
123
139
|
|
|
124
140
|
it 'raises on unknown option' do
|
|
125
|
-
config = IOConfig.new(:
|
|
141
|
+
config = IOConfig.new(abc: 13)
|
|
126
142
|
expect do
|
|
127
143
|
config.for_worker(Abc)
|
|
128
144
|
end.to raise_error(ImageOptim::ConfigurationError)
|
|
@@ -132,11 +148,11 @@ describe ImageOptim::Config do
|
|
|
132
148
|
describe '#initialize' do
|
|
133
149
|
it 'reads options from default locations' do
|
|
134
150
|
expect(IOConfig).to receive(:read_options).
|
|
135
|
-
with(IOConfig::GLOBAL_PATH).and_return(:
|
|
151
|
+
with(IOConfig::GLOBAL_PATH).and_return(a: 1, b: 2, c: 3)
|
|
136
152
|
expect(IOConfig).to receive(:read_options).
|
|
137
|
-
with(IOConfig::LOCAL_PATH).and_return(:
|
|
153
|
+
with(IOConfig::LOCAL_PATH).and_return(a: 10, b: 20)
|
|
138
154
|
|
|
139
|
-
config = IOConfig.new(:
|
|
155
|
+
config = IOConfig.new(a: 100)
|
|
140
156
|
expect(config.get!(:a)).to eq(100)
|
|
141
157
|
expect(config.get!(:b)).to eq(20)
|
|
142
158
|
expect(config.get!(:c)).to eq(3)
|
|
@@ -146,17 +162,17 @@ describe ImageOptim::Config do
|
|
|
146
162
|
it 'does not read options with empty config_paths' do
|
|
147
163
|
expect(IOConfig).not_to receive(:read_options)
|
|
148
164
|
|
|
149
|
-
config = IOConfig.new(:
|
|
165
|
+
config = IOConfig.new(config_paths: [])
|
|
150
166
|
config.assert_no_unused_options!
|
|
151
167
|
end
|
|
152
168
|
|
|
153
169
|
it 'reads options from specified paths' do
|
|
154
170
|
expect(IOConfig).to receive(:read_options).
|
|
155
|
-
with('/etc/image_optim.yml').and_return(:
|
|
171
|
+
with('/etc/image_optim.yml').and_return(a: 1, b: 2, c: 3)
|
|
156
172
|
expect(IOConfig).to receive(:read_options).
|
|
157
|
-
with('config/image_optim.yml').and_return(:
|
|
173
|
+
with('config/image_optim.yml').and_return(a: 10, b: 20)
|
|
158
174
|
|
|
159
|
-
config = IOConfig.new(:
|
|
175
|
+
config = IOConfig.new(a: 100, config_paths: %w[
|
|
160
176
|
/etc/image_optim.yml
|
|
161
177
|
config/image_optim.yml
|
|
162
178
|
])
|
|
@@ -170,7 +186,7 @@ describe ImageOptim::Config do
|
|
|
170
186
|
expect(IOConfig).to receive(:read_options).
|
|
171
187
|
with('config/image_optim.yml').and_return({})
|
|
172
188
|
|
|
173
|
-
config = IOConfig.new(:
|
|
189
|
+
config = IOConfig.new(config_paths: 'config/image_optim.yml')
|
|
174
190
|
config.assert_no_unused_options!
|
|
175
191
|
end
|
|
176
192
|
end
|
|
@@ -200,7 +216,7 @@ describe ImageOptim::Config do
|
|
|
200
216
|
|
|
201
217
|
it 'returns hash with deep symbolised keys from reader' do
|
|
202
218
|
stringified = {'config' => {'this' => true}}
|
|
203
|
-
symbolized = {:
|
|
219
|
+
symbolized = {config: {this: true}}
|
|
204
220
|
|
|
205
221
|
expect(IOConfig).not_to receive(:warn)
|
|
206
222
|
expect(File).to receive(:expand_path).
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'image_optim/elapsed_time'
|
|
5
|
+
|
|
6
|
+
describe ImageOptim::ElapsedTime do
|
|
7
|
+
let(:timeout){ 0.01 }
|
|
8
|
+
|
|
9
|
+
describe '.now' do
|
|
10
|
+
it 'returns incrementing value' do
|
|
11
|
+
expect{ sleep timeout }.to change{ described_class.now }.by_at_least(timeout)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -10,10 +10,10 @@ describe ImageOptim::HashHelpers do
|
|
|
10
10
|
|
|
11
11
|
context 'stringify/simbolyze' do
|
|
12
12
|
symbol_keys = {
|
|
13
|
-
:
|
|
14
|
-
:
|
|
15
|
-
:
|
|
16
|
-
:
|
|
13
|
+
a: 1,
|
|
14
|
+
b: {
|
|
15
|
+
c: [:a, 'a'],
|
|
16
|
+
d: {},
|
|
17
17
|
},
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -38,22 +38,22 @@ describe ImageOptim::HashHelpers do
|
|
|
38
38
|
|
|
39
39
|
it 'deep merges hashes' do
|
|
40
40
|
merge_a = {
|
|
41
|
-
:
|
|
42
|
-
:
|
|
43
|
-
:
|
|
44
|
-
:
|
|
45
|
-
:
|
|
41
|
+
a: {
|
|
42
|
+
b: 1,
|
|
43
|
+
c: {
|
|
44
|
+
d: 2,
|
|
45
|
+
e: {f: true},
|
|
46
46
|
},
|
|
47
47
|
},
|
|
48
|
-
:
|
|
48
|
+
y: 10,
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
merge_b = {
|
|
52
52
|
:a => {
|
|
53
|
-
:
|
|
54
|
-
:
|
|
55
|
-
:
|
|
56
|
-
:
|
|
53
|
+
b: 2,
|
|
54
|
+
c: {
|
|
55
|
+
d: 3,
|
|
56
|
+
e: false,
|
|
57
57
|
},
|
|
58
58
|
},
|
|
59
59
|
'z' => 20,
|
|
@@ -61,10 +61,10 @@ describe ImageOptim::HashHelpers do
|
|
|
61
61
|
|
|
62
62
|
merge_result = {
|
|
63
63
|
:a => {
|
|
64
|
-
:
|
|
65
|
-
:
|
|
66
|
-
:
|
|
67
|
-
:
|
|
64
|
+
b: 2,
|
|
65
|
+
c: {
|
|
66
|
+
d: 3,
|
|
67
|
+
e: false,
|
|
68
68
|
},
|
|
69
69
|
},
|
|
70
70
|
:y => 10,
|
|
@@ -58,13 +58,13 @@ describe ImageOptim::OptionDefinition do
|
|
|
58
58
|
|
|
59
59
|
context 'when option is nil' do
|
|
60
60
|
it 'returns nil' do
|
|
61
|
-
expect(subject.value(nil, :
|
|
61
|
+
expect(subject.value(nil, abc: nil)).to eq(nil)
|
|
62
62
|
end
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
context 'when option is set' do
|
|
66
66
|
it 'returns value' do
|
|
67
|
-
expect(subject.value(nil, :
|
|
67
|
+
expect(subject.value(nil, abc: 123)).to eq(123)
|
|
68
68
|
end
|
|
69
69
|
end
|
|
70
70
|
end
|
|
@@ -84,13 +84,13 @@ describe ImageOptim::OptionDefinition do
|
|
|
84
84
|
|
|
85
85
|
context 'when option is nil' do
|
|
86
86
|
it 'returns nil passed through proc' do
|
|
87
|
-
expect(subject.value(nil, :
|
|
87
|
+
expect(subject.value(nil, abc: nil)).to eq('nil')
|
|
88
88
|
end
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
context 'when option is set' do
|
|
92
92
|
it 'returns value passed through proc' do
|
|
93
|
-
expect(subject.value(nil, :
|
|
93
|
+
expect(subject.value(nil, abc: 123)).to eq('123')
|
|
94
94
|
end
|
|
95
95
|
end
|
|
96
96
|
end
|
|
@@ -108,13 +108,13 @@ describe ImageOptim::OptionDefinition do
|
|
|
108
108
|
|
|
109
109
|
context 'when option is nil' do
|
|
110
110
|
it 'returns nil passed through proc' do
|
|
111
|
-
expect(subject.value(nil, :
|
|
111
|
+
expect(subject.value(nil, abc: nil)).to eq(['nil', subject])
|
|
112
112
|
end
|
|
113
113
|
end
|
|
114
114
|
|
|
115
115
|
context 'when option is set' do
|
|
116
116
|
it 'returns value passed through proc' do
|
|
117
|
-
expect(subject.value(nil, :
|
|
117
|
+
expect(subject.value(nil, abc: 123)).to eq(['123', subject])
|
|
118
118
|
end
|
|
119
119
|
end
|
|
120
120
|
end
|
|
@@ -5,8 +5,6 @@ require 'image_optim/path'
|
|
|
5
5
|
require 'tempfile'
|
|
6
6
|
|
|
7
7
|
describe ImageOptim::Path do
|
|
8
|
-
include CapabilityCheckHelpers
|
|
9
|
-
|
|
10
8
|
before do
|
|
11
9
|
stub_const('Path', ImageOptim::Path)
|
|
12
10
|
end
|
|
@@ -61,45 +59,82 @@ describe ImageOptim::Path do
|
|
|
61
59
|
let(:src){ Path.temp_file_path }
|
|
62
60
|
let(:dst){ Path.temp_file_path }
|
|
63
61
|
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
shared_examples 'replaces file' do
|
|
63
|
+
it 'moves data to destination' do
|
|
64
|
+
src.write('src')
|
|
66
65
|
|
|
67
|
-
|
|
66
|
+
src.replace(dst)
|
|
68
67
|
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
expect(dst.read).to eq('src')
|
|
69
|
+
end
|
|
71
70
|
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
it 'removes original file' do
|
|
72
|
+
src.replace(dst)
|
|
74
73
|
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
expect(src).to_not exist
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it 'preserves attributes of destination file', skip: SkipConditions[:any_file_mode_allowed] do
|
|
78
|
+
mode = 0o666
|
|
79
|
+
|
|
80
|
+
dst.chmod(mode)
|
|
81
|
+
|
|
82
|
+
src.replace(dst)
|
|
83
|
+
|
|
84
|
+
got = dst.stat.mode & 0o777
|
|
85
|
+
expect(got).to eq(mode), format('expected %04o, got %04o', mode, got)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it 'does not preserve mtime of destination file' do
|
|
89
|
+
time = src.mtime
|
|
90
|
+
|
|
91
|
+
dst.utime(time - 1000, time - 1000)
|
|
77
92
|
|
|
78
|
-
|
|
79
|
-
skip 'full file modes are not support' unless any_file_modes_allowed?
|
|
80
|
-
mode = 0o666
|
|
93
|
+
src.replace(dst)
|
|
81
94
|
|
|
82
|
-
|
|
95
|
+
expect(dst.mtime).to be >= time
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it 'changes inode of destination', skip: SkipConditions[:inodes_support] do
|
|
99
|
+
expect{ src.replace(dst) }.to change{ dst.stat.ino }
|
|
100
|
+
end
|
|
101
|
+
end
|
|
83
102
|
|
|
84
|
-
|
|
103
|
+
context 'when src and dst are on same device' do
|
|
104
|
+
before do
|
|
105
|
+
allow_any_instance_of(File::Stat).to receive(:dev).and_return(0)
|
|
106
|
+
end
|
|
85
107
|
|
|
86
|
-
|
|
87
|
-
expect(got).to eq(mode), format('expected %04o, got %04o', mode, got)
|
|
108
|
+
include_examples 'replaces file'
|
|
88
109
|
end
|
|
89
110
|
|
|
90
|
-
|
|
91
|
-
|
|
111
|
+
context 'when src and dst are on different devices' do
|
|
112
|
+
before do
|
|
113
|
+
allow_any_instance_of(File::Stat).to receive(:dev, &:__id__)
|
|
114
|
+
end
|
|
92
115
|
|
|
93
|
-
|
|
116
|
+
include_examples 'replaces file'
|
|
94
117
|
|
|
95
|
-
|
|
118
|
+
it 'is using temporary file with .tmp extension' do
|
|
119
|
+
expect(src).to receive(:move).with(having_attributes(extname: '.tmp'))
|
|
96
120
|
|
|
97
|
-
|
|
121
|
+
src.replace(dst)
|
|
122
|
+
end
|
|
98
123
|
end
|
|
99
124
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
125
|
+
context 'when src and dst are on same device, but rename causes Errno::EXDEV' do
|
|
126
|
+
before do
|
|
127
|
+
allow_any_instance_of(File::Stat).to receive(:dev).and_return(0)
|
|
128
|
+
expect(src).to receive(:rename).with(dst.to_s).once.and_raise(Errno::EXDEV)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
include_examples 'replaces file'
|
|
132
|
+
|
|
133
|
+
it 'is using temporary file with .tmp extension' do
|
|
134
|
+
expect(src).to receive(:move).with(having_attributes(extname: '.tmp'))
|
|
135
|
+
|
|
136
|
+
src.replace(dst)
|
|
137
|
+
end
|
|
103
138
|
end
|
|
104
139
|
end
|
|
105
140
|
end
|