image_optim 0.27.1 → 0.31.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/.github/workflows/check.yml +59 -0
- data/.pre-commit-hooks.yaml +9 -0
- data/.rubocop.yml +6 -3
- data/CHANGELOG.markdown +22 -0
- data/CONTRIBUTING.markdown +5 -2
- data/Gemfile +1 -7
- data/LICENSE.txt +1 -1
- data/README.markdown +21 -10
- data/Vagrantfile +1 -1
- data/image_optim.gemspec +7 -4
- data/lib/image_optim/bin_resolver/bin.rb +10 -9
- data/lib/image_optim/cache.rb +6 -0
- data/lib/image_optim/cmd.rb +45 -6
- data/lib/image_optim/config.rb +14 -8
- data/lib/image_optim/elapsed_time.rb +26 -0
- data/lib/image_optim/errors.rb +9 -0
- data/lib/image_optim/path.rb +1 -1
- data/lib/image_optim/runner/option_parser.rb +24 -18
- data/lib/image_optim/runner.rb +1 -1
- data/lib/image_optim/timer.rb +25 -0
- data/lib/image_optim/worker/advpng.rb +7 -7
- data/lib/image_optim/worker/gifsicle.rb +11 -11
- data/lib/image_optim/worker/jhead.rb +2 -2
- data/lib/image_optim/worker/jpegoptim.rb +13 -11
- data/lib/image_optim/worker/jpegrecompress.rb +17 -2
- data/lib/image_optim/worker/jpegtran.rb +4 -4
- data/lib/image_optim/worker/optipng.rb +7 -7
- data/lib/image_optim/worker/oxipng.rb +53 -0
- data/lib/image_optim/worker/pngcrush.rb +6 -6
- data/lib/image_optim/worker/pngout.rb +7 -7
- data/lib/image_optim/worker/pngquant.rb +10 -9
- data/lib/image_optim/worker/svgo.rb +2 -2
- data/lib/image_optim/worker.rb +32 -29
- data/lib/image_optim.rb +16 -10
- data/script/update_worker_options_in_readme +2 -2
- data/script/worker_analysis +16 -18
- data/spec/image_optim/bin_resolver_spec.rb +5 -5
- data/spec/image_optim/cache_path_spec.rb +7 -10
- data/spec/image_optim/cache_spec.rb +8 -8
- data/spec/image_optim/cmd_spec.rb +64 -6
- data/spec/image_optim/config_spec.rb +36 -20
- data/spec/image_optim/elapsed_time_spec.rb +14 -0
- data/spec/image_optim/handler_spec.rb +1 -1
- 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 +8 -11
- 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/oxipng_spec.rb +89 -0
- 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 +47 -10
- data/spec/images/invisiblepixels/generate +1 -1
- data/spec/spec_helper.rb +18 -17
- metadata +36 -15
- data/.travis.yml +0 -49
data/lib/image_optim/worker.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
|
4
4
|
require 'image_optim/cmd'
|
5
5
|
require 'image_optim/configuration_error'
|
6
|
+
require 'image_optim/elapsed_time'
|
6
7
|
require 'image_optim/path'
|
7
8
|
require 'image_optim/worker/class_methods'
|
8
9
|
require 'shellwords'
|
@@ -41,7 +42,7 @@ class ImageOptim
|
|
41
42
|
|
42
43
|
# Optimize image at src, output at dst, must be overriden in subclass
|
43
44
|
# return true on success
|
44
|
-
def optimize(_src, _dst)
|
45
|
+
def optimize(_src, _dst, options = {})
|
45
46
|
fail NotImplementedError, "implement method optimize in #{self.class}"
|
46
47
|
end
|
47
48
|
|
@@ -104,7 +105,7 @@ class ImageOptim
|
|
104
105
|
return if unknown_options.empty?
|
105
106
|
|
106
107
|
fail ConfigurationError, "unknown options #{unknown_options.inspect} "\
|
107
|
-
|
108
|
+
"for #{self}"
|
108
109
|
end
|
109
110
|
|
110
111
|
# Forward bin resolving to image_optim
|
@@ -117,47 +118,49 @@ class ImageOptim
|
|
117
118
|
def wrap_resolver_error_message(message)
|
118
119
|
name = self.class.bin_sym
|
119
120
|
"#{name} worker: #{message}; please provide proper binary or "\
|
120
|
-
|
121
|
-
|
121
|
+
"disable this worker (--no-#{name} argument or "\
|
122
|
+
"`:#{name} => false` through options)"
|
122
123
|
end
|
123
124
|
|
124
125
|
# Run command setting priority and hiding output
|
125
|
-
def execute(bin,
|
126
|
+
def execute(bin, arguments, options)
|
126
127
|
resolve_bin!(bin)
|
127
128
|
|
128
129
|
cmd_args = [bin, *arguments].map(&:to_s)
|
129
130
|
|
130
|
-
start = Time.now
|
131
|
-
|
132
|
-
success = run_command(cmd_args)
|
133
|
-
|
134
131
|
if @image_optim.verbose
|
135
|
-
|
136
|
-
|
132
|
+
run_command_verbose(cmd_args, options)
|
133
|
+
else
|
134
|
+
run_command(cmd_args, options)
|
137
135
|
end
|
138
|
-
|
139
|
-
success
|
140
136
|
end
|
141
137
|
|
142
138
|
# Run command defining environment, setting nice level, removing output and
|
143
139
|
# reraising signal exception
|
144
|
-
def run_command(cmd_args)
|
145
|
-
args =
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
].join(' ')
|
152
|
-
else
|
153
|
-
[
|
154
|
-
{'PATH' => @image_optim.env_path},
|
155
|
-
%W[nice -n #{@image_optim.nice}],
|
156
|
-
cmd_args,
|
157
|
-
{:out => Path::NULL, :err => Path::NULL},
|
158
|
-
].flatten
|
159
|
-
end
|
140
|
+
def run_command(cmd_args, options)
|
141
|
+
args = [
|
142
|
+
{'PATH' => @image_optim.env_path},
|
143
|
+
*%W[nice -n #{@image_optim.nice}],
|
144
|
+
*cmd_args,
|
145
|
+
options.merge(out: Path::NULL, err: Path::NULL),
|
146
|
+
]
|
160
147
|
Cmd.run(*args)
|
161
148
|
end
|
149
|
+
|
150
|
+
# Wrap run_command and output status, elapsed time and command
|
151
|
+
def run_command_verbose(cmd_args, options)
|
152
|
+
start = ElapsedTime.now
|
153
|
+
|
154
|
+
begin
|
155
|
+
success = run_command(cmd_args, options)
|
156
|
+
status = success ? '✓' : '✗'
|
157
|
+
success
|
158
|
+
rescue Errors::TimeoutExceeded
|
159
|
+
status = 'timeout'
|
160
|
+
raise
|
161
|
+
ensure
|
162
|
+
$stderr << format("%s %.1fs %s\n", status, ElapsedTime.now - start, cmd_args.shelljoin)
|
163
|
+
end
|
164
|
+
end
|
162
165
|
end
|
163
166
|
end
|
data/lib/image_optim.rb
CHANGED
@@ -3,16 +3,18 @@
|
|
3
3
|
require 'image_optim/bin_resolver'
|
4
4
|
require 'image_optim/cache'
|
5
5
|
require 'image_optim/config'
|
6
|
+
require 'image_optim/errors'
|
6
7
|
require 'image_optim/handler'
|
7
8
|
require 'image_optim/image_meta'
|
8
9
|
require 'image_optim/optimized_path'
|
9
10
|
require 'image_optim/path'
|
11
|
+
require 'image_optim/timer'
|
10
12
|
require 'image_optim/worker'
|
11
13
|
require 'in_threads'
|
12
14
|
require 'shellwords'
|
13
15
|
|
14
16
|
%w[
|
15
|
-
pngcrush pngout advpng optipng pngquant
|
17
|
+
pngcrush pngout advpng optipng pngquant oxipng
|
16
18
|
jhead jpegoptim jpegrecompress jpegtran
|
17
19
|
gifsicle
|
18
20
|
svgo
|
@@ -46,6 +48,9 @@ class ImageOptim
|
|
46
48
|
# Cache worker digests
|
47
49
|
attr_reader :cache_worker_digests
|
48
50
|
|
51
|
+
# Timeout in seconds for each image
|
52
|
+
attr_reader :timeout
|
53
|
+
|
49
54
|
# Initialize workers, specify options using worker underscored name:
|
50
55
|
#
|
51
56
|
# pass false to disable worker
|
@@ -78,6 +83,7 @@ class ImageOptim
|
|
78
83
|
allow_lossy
|
79
84
|
cache_dir
|
80
85
|
cache_worker_digests
|
86
|
+
timeout
|
81
87
|
].each do |name|
|
82
88
|
instance_variable_set(:"@#{name}", config.send(name))
|
83
89
|
$stderr << "#{name}: #{send(name)}\n" if verbose
|
@@ -110,11 +116,17 @@ class ImageOptim
|
|
110
116
|
return unless (workers = workers_for_image(original))
|
111
117
|
|
112
118
|
optimized = @cache.fetch(original) do
|
119
|
+
timer = timeout && Timer.new(timeout)
|
120
|
+
|
113
121
|
Handler.for(original) do |handler|
|
114
|
-
|
115
|
-
|
116
|
-
|
122
|
+
begin
|
123
|
+
workers.each do |worker|
|
124
|
+
handler.process do |src, dst|
|
125
|
+
worker.optimize(src, dst, timeout: timer)
|
126
|
+
end
|
117
127
|
end
|
128
|
+
rescue Errors::TimeoutExceeded
|
129
|
+
handler.result
|
118
130
|
end
|
119
131
|
end
|
120
132
|
end
|
@@ -188,12 +200,6 @@ class ImageOptim
|
|
188
200
|
optimize_image_method?(method) || super
|
189
201
|
end
|
190
202
|
|
191
|
-
if RUBY_VERSION < '1.9'
|
192
|
-
def respond_to?(method, include_private = false)
|
193
|
-
optimize_image_method?(method) || super
|
194
|
-
end
|
195
|
-
end
|
196
|
-
|
197
203
|
# Version of image_optim gem spec loaded
|
198
204
|
def version
|
199
205
|
Gem.loaded_specs['image_optim'].version.to_s
|
@@ -18,7 +18,7 @@ def write_worker_options(io, klass)
|
|
18
18
|
klass.option_definitions.each do |option_definition|
|
19
19
|
line = "* `:#{option_definition.name}` — #{option_definition.description}"
|
20
20
|
unless line['(defaults']
|
21
|
-
line
|
21
|
+
line += " *(defaults to #{option_definition.default_description})*"
|
22
22
|
end
|
23
23
|
io.puts line
|
24
24
|
end
|
@@ -29,7 +29,7 @@ end
|
|
29
29
|
def write_marked(io)
|
30
30
|
io.puts BEGIN_MARKER
|
31
31
|
io.puts '<!-- markdown for worker options is generated by '\
|
32
|
-
|
32
|
+
"`#{Pathname($PROGRAM_NAME).cleanpath}` -->"
|
33
33
|
io.puts
|
34
34
|
|
35
35
|
ImageOptim::Worker.klasses.sort_by(&:name).each do |klass|
|
data/script/worker_analysis
CHANGED
@@ -358,20 +358,18 @@ class Analyser
|
|
358
358
|
end
|
359
359
|
|
360
360
|
def flatten_animation(image)
|
361
|
-
run_cache[:flatten][image.digest] ||=
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
image
|
374
|
-
end
|
361
|
+
run_cache[:flatten][image.digest] ||= if image.image_format == :gif
|
362
|
+
flattened = image.temp_path
|
363
|
+
Cmd.run(*%W[
|
364
|
+
convert
|
365
|
+
#{image.image_format}:#{image}
|
366
|
+
-coalesce
|
367
|
+
-append
|
368
|
+
#{image.image_format}:#{flattened}
|
369
|
+
]) || fail("failed flattening of #{image}")
|
370
|
+
flattened
|
371
|
+
else
|
372
|
+
image
|
375
373
|
end
|
376
374
|
end
|
377
375
|
|
@@ -527,10 +525,10 @@ class Analyser
|
|
527
525
|
stats = Stats.new('all', by_format[format], worker_ids2names)
|
528
526
|
path = FSPath("#{DIR}/#{basenames[format]}")
|
529
527
|
model = {
|
530
|
-
:
|
531
|
-
:
|
532
|
-
:
|
533
|
-
:
|
528
|
+
stats_format: format,
|
529
|
+
stats: stats,
|
530
|
+
format_links: basenames,
|
531
|
+
template_dir: template_path.dirname.relative_path_from(path.dirname),
|
534
532
|
}
|
535
533
|
html = template.result(OpenStruct.new(model).instance_eval{ binding })
|
536
534
|
path.write(html)
|
@@ -19,7 +19,7 @@ describe ImageOptim::BinResolver do
|
|
19
19
|
allow(ENV).to receive(:[]).and_call_original
|
20
20
|
end
|
21
21
|
|
22
|
-
let(:image_optim){ double(:image_optim, :
|
22
|
+
let(:image_optim){ double(:image_optim, verbose: false, pack: false) }
|
23
23
|
let(:resolver){ BinResolver.new(image_optim) }
|
24
24
|
|
25
25
|
describe '#full_path' do
|
@@ -130,7 +130,7 @@ describe ImageOptim::BinResolver do
|
|
130
130
|
it 'resolves bin specified in ENV' do
|
131
131
|
path = 'bin/the_optimizer'
|
132
132
|
stub_env 'THE_OPTIMIZER_BIN', path
|
133
|
-
tmpdir = double(:tmpdir, :
|
133
|
+
tmpdir = double(:tmpdir, to_str: 'tmpdir')
|
134
134
|
symlink = double(:symlink)
|
135
135
|
|
136
136
|
full_path = File.expand_path(path)
|
@@ -180,9 +180,9 @@ describe ImageOptim::BinResolver do
|
|
180
180
|
stub_env 'THE_OPTIMIZER_BIN', path
|
181
181
|
expect(FSPath).not_to receive(:temp_dir)
|
182
182
|
expect(resolver).not_to receive(:at_exit)
|
183
|
-
allow(File).to receive_messages(
|
184
|
-
|
185
|
-
|
183
|
+
allow(File).to receive_messages(exist?: exist?,
|
184
|
+
file?: file?,
|
185
|
+
executable?: executable?)
|
186
186
|
end
|
187
187
|
|
188
188
|
after do
|
@@ -5,8 +5,6 @@ require 'image_optim/cache_path'
|
|
5
5
|
require 'tempfile'
|
6
6
|
|
7
7
|
describe ImageOptim::CachePath do
|
8
|
-
include CapabilityCheckHelpers
|
9
|
-
|
10
8
|
before do
|
11
9
|
stub_const('Path', ImageOptim::Path)
|
12
10
|
stub_const('CachePath', ImageOptim::CachePath)
|
@@ -32,8 +30,7 @@ describe ImageOptim::CachePath do
|
|
32
30
|
expect(src).to exist
|
33
31
|
end
|
34
32
|
|
35
|
-
it 'preserves attributes of destination file' do
|
36
|
-
skip 'full file modes are not support' unless any_file_modes_allowed?
|
33
|
+
it 'preserves attributes of destination file', skip: SkipConditions[:any_file_mode_allowed] do
|
37
34
|
mode = 0o666
|
38
35
|
|
39
36
|
dst.chmod(mode)
|
@@ -45,22 +42,22 @@ describe ImageOptim::CachePath do
|
|
45
42
|
end
|
46
43
|
|
47
44
|
it 'does not preserve mtime of destination file' do
|
48
|
-
time = src.mtime
|
45
|
+
time = src.mtime - 1000
|
46
|
+
dst.utime(time, time)
|
49
47
|
|
50
|
-
|
48
|
+
time = dst.mtime
|
51
49
|
|
52
50
|
src.replace(dst)
|
53
51
|
|
54
|
-
expect(dst.mtime).
|
52
|
+
expect(dst.mtime).to_not eq(time)
|
55
53
|
end
|
56
54
|
|
57
|
-
it 'changes inode of destination' do
|
58
|
-
skip 'inodes are not supported' unless inodes_supported?
|
55
|
+
it 'changes inode of destination', skip: SkipConditions[:inodes_support] do
|
59
56
|
expect{ src.replace(dst) }.to change{ dst.stat.ino }
|
60
57
|
end
|
61
58
|
|
62
59
|
it 'is using temporary file with .tmp extension' do
|
63
|
-
expect(src).to receive(:copy).with(having_attributes(:
|
60
|
+
expect(src).to receive(:copy).with(having_attributes(extname: '.tmp')).at_least(:once)
|
64
61
|
|
65
62
|
src.replace(dst)
|
66
63
|
end
|
@@ -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
|
|
@@ -102,7 +102,7 @@ describe ImageOptim::Cache do
|
|
102
102
|
expect(FileUtils).not_to receive(:mv)
|
103
103
|
expect(File).not_to receive(:rename)
|
104
104
|
|
105
|
-
expect(cache.fetch(original){}).to eq(cached)
|
105
|
+
expect(cache.fetch(original){ nil }).to eq(cached)
|
106
106
|
end
|
107
107
|
|
108
108
|
it 'returns nil when file is already optimized' do
|
@@ -122,7 +122,7 @@ describe ImageOptim::Cache do
|
|
122
122
|
context 'when cache is enabled (without worker digests)' do
|
123
123
|
let(:image_optim) do
|
124
124
|
double(:image_optim,
|
125
|
-
:cache_dir
|
125
|
+
cache_dir: cache_dir, cache_worker_digests: false, timeout: nil)
|
126
126
|
end
|
127
127
|
let(:cache) do
|
128
128
|
cache = Cache.new(image_optim, {})
|
@@ -144,8 +144,8 @@ describe ImageOptim::Cache do
|
|
144
144
|
context 'when cache is enabled (with worker digests)' do
|
145
145
|
let(:image_optim) do
|
146
146
|
double(:image_optim,
|
147
|
-
:
|
148
|
-
:
|
147
|
+
cache_dir: cache_dir,
|
148
|
+
cache_worker_digests: true, timeout: nil)
|
149
149
|
end
|
150
150
|
let(:cache) do
|
151
151
|
cache = Cache.new(image_optim, {})
|
@@ -2,10 +2,9 @@
|
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
4
|
require 'image_optim/cmd'
|
5
|
+
require 'image_optim/timer'
|
5
6
|
|
6
7
|
describe ImageOptim::Cmd do
|
7
|
-
include CapabilityCheckHelpers
|
8
|
-
|
9
8
|
before do
|
10
9
|
stub_const('Cmd', ImageOptim::Cmd)
|
11
10
|
end
|
@@ -35,12 +34,72 @@ describe ImageOptim::Cmd do
|
|
35
34
|
expect($CHILD_STATUS.exitstatus).to eq(66)
|
36
35
|
end
|
37
36
|
|
38
|
-
it 'raises SignalException if process terminates after signal' do
|
39
|
-
skip 'signals are not supported' unless signals_supported?
|
37
|
+
it 'raises SignalException if process terminates after signal', skip: SkipConditions[:signals_support] do
|
40
38
|
expect_int_exception do
|
41
39
|
Cmd.run('kill -s INT $$')
|
42
40
|
end
|
43
41
|
end
|
42
|
+
|
43
|
+
context 'with timeout' do
|
44
|
+
it 'returns process success status' do
|
45
|
+
expect(Cmd.run('sh -c "exit 0"', timeout: 1)).to eq(true)
|
46
|
+
|
47
|
+
expect(Cmd.run('sh -c "exit 1"', timeout: 1)).to eq(false)
|
48
|
+
|
49
|
+
expect(Cmd.run('sh -c "exit 66"', timeout: 1)).to eq(false)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'returns process success status when timeout is instance of ImageOptim::Timer' do
|
53
|
+
timeout = ImageOptim::Timer.new(1.0)
|
54
|
+
expect(Cmd.run('sh -c "exit 0"', timeout: timeout)).to eq(true)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'raises SignalException if process terminates after signal', skip: SkipConditions[:signals_support] do
|
58
|
+
expect_int_exception do
|
59
|
+
Cmd.run('kill -s INT $$', timeout: 1)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'raises TimeoutExceeded if process does not exit until timeout' do
|
64
|
+
expect do
|
65
|
+
Cmd.run('sleep 10', timeout: 0)
|
66
|
+
end.to raise_error(ImageOptim::Errors::TimeoutExceeded)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'does not leave zombie threads' do
|
70
|
+
expect do
|
71
|
+
begin
|
72
|
+
Cmd.run('sleep 10', timeout: 0)
|
73
|
+
rescue ImageOptim::Errors::TimeoutExceeded
|
74
|
+
# noop
|
75
|
+
end
|
76
|
+
end.not_to change{ Thread.list }
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'receives TERM', skip: SkipConditions[:signals_support] do
|
80
|
+
waiter = double
|
81
|
+
allow(Process).to receive(:detach).once{ |pid| @pid = pid; waiter }
|
82
|
+
allow(waiter).to receive(:join){ sleep 0.1; nil }
|
83
|
+
|
84
|
+
expect do
|
85
|
+
Cmd.run('sleep 5', timeout: 0.1)
|
86
|
+
end.to raise_error(ImageOptim::Errors::TimeoutExceeded)
|
87
|
+
|
88
|
+
expect(Process.wait2(@pid).last.termsig).to eq(Signal.list['TERM'])
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'receives KILL if it does not react on TERM', skip: SkipConditions[:signals_support] do
|
92
|
+
waiter = double
|
93
|
+
allow(Process).to receive(:detach).once{ |pid| @pid = pid; waiter }
|
94
|
+
allow(waiter).to receive(:join){ sleep 0.1; nil }
|
95
|
+
|
96
|
+
expect do
|
97
|
+
Cmd.run('trap "" TERM; sleep 5', timeout: 0.1)
|
98
|
+
end.to raise_error(ImageOptim::Errors::TimeoutExceeded)
|
99
|
+
|
100
|
+
expect(Process.wait2(@pid).last.termsig).to eq(Signal.list['KILL'])
|
101
|
+
end
|
102
|
+
end
|
44
103
|
end
|
45
104
|
|
46
105
|
describe '.capture' do
|
@@ -62,8 +121,7 @@ describe ImageOptim::Cmd do
|
|
62
121
|
expect($CHILD_STATUS.exitstatus).to eq(66)
|
63
122
|
end
|
64
123
|
|
65
|
-
it 'raises SignalException if process terminates after signal' do
|
66
|
-
skip 'signals are not supported' unless signals_supported?
|
124
|
+
it 'raises SignalException if process terminates after signal', skip: SkipConditions[:signals_support] do
|
67
125
|
expect_int_exception do
|
68
126
|
Cmd.capture('kill -s INT $$')
|
69
127
|
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
|
@@ -9,7 +9,7 @@ describe ImageOptim::Handler do
|
|
9
9
|
end
|
10
10
|
|
11
11
|
it 'uses original as source for first conversion '\
|
12
|
-
|
12
|
+
'and two temp files for further conversions' do
|
13
13
|
original = double(:original)
|
14
14
|
allow(original).to receive(:respond_to?).with(:temp_path).and_return(true)
|
15
15
|
|