image_optim 0.17.1 → 0.18.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.
Files changed (43) hide show
  1. checksums.yaml +8 -8
  2. data/.gitignore +1 -0
  3. data/.travis.yml +5 -18
  4. data/CHANGELOG.markdown +10 -0
  5. data/README.markdown +31 -1
  6. data/bin/image_optim +3 -137
  7. data/image_optim.gemspec +6 -3
  8. data/lib/image_optim.rb +20 -3
  9. data/lib/image_optim/bin_resolver.rb +28 -1
  10. data/lib/image_optim/bin_resolver/bin.rb +17 -7
  11. data/lib/image_optim/cmd.rb +49 -0
  12. data/lib/image_optim/config.rb +64 -4
  13. data/lib/image_optim/image_path.rb +5 -0
  14. data/lib/image_optim/option_definition.rb +5 -3
  15. data/lib/image_optim/runner.rb +1 -2
  16. data/lib/image_optim/runner/option_parser.rb +216 -0
  17. data/lib/image_optim/worker.rb +32 -17
  18. data/lib/image_optim/worker/advpng.rb +7 -1
  19. data/lib/image_optim/worker/gifsicle.rb +16 -3
  20. data/lib/image_optim/worker/jhead.rb +15 -8
  21. data/lib/image_optim/worker/jpegoptim.rb +6 -2
  22. data/lib/image_optim/worker/jpegtran.rb +10 -3
  23. data/lib/image_optim/worker/optipng.rb +6 -1
  24. data/lib/image_optim/worker/pngcrush.rb +8 -1
  25. data/lib/image_optim/worker/pngout.rb +8 -1
  26. data/lib/image_optim/worker/svgo.rb +4 -1
  27. data/script/worker_analysis +523 -0
  28. data/script/worker_analysis.haml +153 -0
  29. data/spec/image_optim/bin_resolver/comparable_condition_spec.rb +4 -5
  30. data/spec/image_optim/bin_resolver/simple_version_spec.rb +44 -21
  31. data/spec/image_optim/bin_resolver_spec.rb +63 -29
  32. data/spec/image_optim/cmd_spec.rb +66 -0
  33. data/spec/image_optim/config_spec.rb +38 -38
  34. data/spec/image_optim/handler_spec.rb +15 -12
  35. data/spec/image_optim/hash_helpers_spec.rb +14 -13
  36. data/spec/image_optim/image_path_spec.rb +22 -7
  37. data/spec/image_optim/runner/glob_helpers_spec.rb +6 -5
  38. data/spec/image_optim/runner/option_parser_spec.rb +99 -0
  39. data/spec/image_optim/space_spec.rb +5 -4
  40. data/spec/image_optim/worker_spec.rb +6 -5
  41. data/spec/image_optim_spec.rb +209 -237
  42. data/spec/spec_helper.rb +3 -0
  43. metadata +43 -11
@@ -0,0 +1,153 @@
1
+ !!! 5
2
+ %html
3
+ %head
4
+ %meta(http-equiv="Content-Type" content="text/html; charset=utf-8")
5
+ %title Worker Analysis
6
+ :css
7
+ body {
8
+ font: 14px "Myriad Pro", "Helvetica", Arial, sans-serif;
9
+ margin: 20px;
10
+ padding: 0;
11
+ }
12
+
13
+ .formats {
14
+ display: block;
15
+ font-size: 1.5em;
16
+ margin: 1em 0;
17
+ font-weight: bold;
18
+ }
19
+
20
+ table {
21
+ border-collapse: collapse;
22
+ border-spacing: 0;
23
+ }
24
+ th, td {
25
+ border: 1px solid #ccc;
26
+ padding: 6px 8px 2px;
27
+ }
28
+ th {
29
+ text-align: left;
30
+ border-color: #999;
31
+ }
32
+ th:not([data-sortable="false"]) {
33
+ cursor: pointer;
34
+ padding-right: 18px;
35
+ }
36
+ th:after {
37
+ position: absolute;
38
+ width: 18px;
39
+ height: 14px;
40
+ line-height: 14px;
41
+ font-size: 0.8em;
42
+ color: #999;
43
+ visibility: hidden;
44
+ text-align: center;
45
+ }
46
+ th[data-sorted="true"]:after {
47
+ visibility: visible;
48
+ }
49
+ th[data-sorted-direction="descending"]:after {
50
+ content: "▼";
51
+ }
52
+ th[data-sorted-direction="ascending"]:after {
53
+ content: "▲";
54
+ }
55
+ table[data-sortable] tbody tr:hover {
56
+ background: #ddf;
57
+ }
58
+ tr.unused {
59
+ display: none;
60
+ }
61
+
62
+ input { margin-top: 1.5em; }
63
+ #toggle-show-unused:checked ~ table tr.unused { /* without js */
64
+ display: table-row;
65
+ }
66
+
67
+ .number {
68
+ text-align: right;
69
+ }
70
+
71
+ .warn-low {
72
+ background: rgba(255, 0, 0, 0.1);
73
+ }
74
+ .warn-medium {
75
+ background: rgba(255, 0, 0, 0.4);
76
+ }
77
+ .warn-high {
78
+ background: rgba(255, 0, 0, 0.8);
79
+ }
80
+
81
+ .worker-name,
82
+ .worker-success-count {
83
+ display: inline-block;
84
+ border-radius: 8px;
85
+ }
86
+ .worker-name {
87
+ background: #ddd;
88
+ padding: 2px 5px 0;
89
+ border: 1px solid #999;
90
+ margin: -3px 0 1px 0;
91
+ }
92
+ .worker-success-count {
93
+ font-size: 0.9em;
94
+ line-height: 1em;
95
+ background: #dfd;
96
+ padding: 1px 2px 0;
97
+ border: 1px solid #9b9;
98
+ }
99
+ .worker-name.unused {
100
+ text-decoration: line-through;
101
+ }
102
+ :javascript
103
+ /*! sortable.js 0.6.0 */
104
+ (function(){var a,b,c,d,e,f,g;a="table[data-sortable]",d=/^-?[£$¤]?[\d,.]+%?$/,g=/^\s+|\s+$/g,f="ontouchstart"in document.documentElement,c=f?"touchstart":"click",b=function(a,b,c){return null!=a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent("on"+b,c)},e={init:function(b){var c,d,f,g,h;for(null==b&&(b={}),null==b.selector&&(b.selector=a),d=document.querySelectorAll(b.selector),h=[],f=0,g=d.length;g>f;f++)c=d[f],h.push(e.initTable(c));return h},initTable:function(a){var b,c,d,f,g,h;if(1===(null!=(h=a.tHead)?h.rows.length:void 0)&&"true"!==a.getAttribute("data-sortable-initialized")){for(a.setAttribute("data-sortable-initialized","true"),d=a.querySelectorAll("th"),b=f=0,g=d.length;g>f;b=++f)c=d[b],"false"!==c.getAttribute("data-sortable")&&e.setupClickableTH(a,c,b);return a}},setupClickableTH:function(a,d,f){var g;return g=e.getColumnType(a,f),"click"===c&&b(d,"mousedown",function(){return event.preventDefault?event.preventDefault():event.returnValue=!1}),b(d,c,function(){var b,c,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v;for("true"===this.getAttribute("data-sorted")?(l=this.getAttribute("data-sorted-direction"),b="ascending"===l?"descending":"ascending"):b=this.getAttribute("data-default-direction")||g.defaultSortDirection,n=this.parentNode.querySelectorAll("th"),o=0,r=n.length;r>o;o++)d=n[o],d.setAttribute("data-sorted","false"),d.removeAttribute("data-sorted-direction");for(this.setAttribute("data-sorted","true"),this.setAttribute("data-sorted-direction",b),m=a.tBodies[0],i=[],u=m.rows,c=p=0,s=u.length;s>p;c=++p)h=u[c],i.push([e.getNodeValue(h.cells[f]),h,c]);for(k="descending"===b?-1:1,i.sort(function(a,b){var c;return c=g.compare(a,b),0!==c?c*k:a[2]-b[2]}),v=[],q=0,t=i.length;t>q;q++)j=i[q],v.push(m.appendChild(j[1]));return v})},getColumnType:function(a,b){var c,f,g,h,i;for(i=a.tBodies[0].rows,g=0,h=i.length;h>g;g++)if(c=i[g],f=e.getNodeValue(c.cells[b]),""!==f){if(f.match(d))return e.types.numeric;if(!isNaN(Date.parse(f)))return e.types.date}return e.types.alpha},getNodeValue:function(a){return a?null!==a.getAttribute("data-value")?a.getAttribute("data-value"):"undefined"!=typeof a.innerText?a.innerText.replace(g,""):a.textContent.replace(g,""):""},types:{numeric:{defaultSortDirection:"descending",compare:function(a,b){var c,d;return c=parseFloat(a[0].replace(/[^0-9.-]/g,""),10),d=parseFloat(b[0].replace(/[^0-9.-]/g,""),10),isNaN(c)&&(c=0),isNaN(d)&&(d=0),c-d}},alpha:{defaultSortDirection:"ascending",compare:function(a,b){return a[0].localeCompare(b[0])}},date:{defaultSortDirection:"ascending",compare:function(a,b){var c,d;return c=Date.parse(a[0]),d=Date.parse(b[0]),isNaN(c)&&(c=0),isNaN(d)&&(d=0),c-d}}}},setTimeout(e.init,0),window.Sortable=e}).call(this);
105
+ %body
106
+ .formats
107
+ - format_links.each do |format, link|
108
+ - if stats_format == format
109
+ %span= format
110
+ - else
111
+ %a(href="#{link}")= format
112
+ %table
113
+ %tr
114
+ %td.warn-low Max difference >= 0.001
115
+ %td.warn-medium Max difference >= 0.01
116
+ %td.warn-high Max difference >= 0.1
117
+ %input#toggle-show-unused(type="checkbox")
118
+ %label(for="toggle-show-unused")
119
+ Show chains with workers failed for every image
120
+ %p
121
+ Images: #{stats.each_chain.first.entry_count},
122
+ size: #{stats.each_chain.first.original_size}
123
+ %table(data-sortable)
124
+ %thead
125
+ %tr
126
+ %th Chain
127
+ %th(data-default-direction="ascending"
128
+ data-sorted="true"
129
+ data-sorted-direction="ascending") Optimized size
130
+ %th(data-default-direction="ascending") Ratio
131
+ %th(data-default-direction="ascending") Avg ratio
132
+ %th(data-default-direction="ascending") Time (s)
133
+ %th(data-default-direction="ascending") Avg difference
134
+ %th(data-default-direction="ascending") Max difference
135
+ %th Speed (B/s)
136
+ %tbody
137
+ - stats.each_chain do |chain|
138
+ %tr{class: chain.unused_workers && 'unused'}
139
+ %td
140
+ - chain.worker_stats.each do |worker_stat|
141
+ .worker-name{:class => worker_stat.unused? && 'unused'}
142
+ = worker_stat.name
143
+ - unless worker_stat.unused?
144
+ .worker-success-count(title="successfull count")
145
+ = worker_stat.success_count
146
+ %td.number= chain.optimized_size
147
+ %td.number= format '%.4f', chain.ratio
148
+ %td.number= format '%.4f', chain.avg_ratio
149
+ %td.number= format '%.2f', chain.time
150
+ %td.number= format '%.5f', chain.avg_difference
151
+ %td.number{class: chain.warn_level && "warn-#{chain.warn_level}"}
152
+ = format '%.5f', chain.max_difference
153
+ %td.number= format '%.2f', chain.speed
@@ -1,11 +1,10 @@
1
- $LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__)
2
- require 'rspec'
1
+ require 'spec_helper'
3
2
  require 'image_optim/bin_resolver/comparable_condition'
4
3
 
5
4
  describe ImageOptim::BinResolver::ComparableCondition do
6
5
  is = ImageOptim::BinResolver::ComparableCondition.is
7
6
 
8
- it 'should build conditions' do
7
+ it 'builds conditions' do
9
8
  expect(is.between?(10, 20).method).to eq(:between?)
10
9
  expect(is.between?(10, 20).args).to eq([10, 20])
11
10
 
@@ -16,13 +15,13 @@ describe ImageOptim::BinResolver::ComparableCondition do
16
15
  expect((is < 30).args).to eq([30])
17
16
  end
18
17
 
19
- it 'should stringify conditions' do
18
+ it 'stringifies conditions' do
20
19
  expect(is.between?(10, 20).to_s).to eq('10..20')
21
20
  expect((is >= 15).to_s).to eq('>= 15')
22
21
  expect((is < 30).to_s).to eq('< 30')
23
22
  end
24
23
 
25
- it 'should match conditions' do
24
+ it 'matches conditions' do
26
25
  expect(is.between?(10, 20)).not_to match 9
27
26
  expect(is.between?(10, 20)).to match 15
28
27
  expect(is.between?(10, 20)).not_to match 21
@@ -1,34 +1,57 @@
1
- $LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__)
2
- require 'rspec'
1
+ require 'spec_helper'
3
2
  require 'image_optim/bin_resolver/simple_version'
4
3
 
5
4
  describe ImageOptim::BinResolver::SimpleVersion do
6
- def v(str)
7
- ImageOptim::BinResolver::SimpleVersion.new(str)
5
+ helpers = Module.new do
6
+ def v(str)
7
+ ImageOptim::BinResolver::SimpleVersion.new(str)
8
+ end
8
9
  end
10
+ include helpers
11
+ extend helpers
12
+
13
+ describe 'compares version 1.17' do
14
+ subject{ v '1.17' }
9
15
 
10
- it 'should compare versions' do
11
- expect(v '1.17').to be > '0'
12
- expect(v '1.17').to be > '0.1'
13
- expect(v '1.17').to be > '0.9'
14
- expect(v '1.17').to be > '1.9'
15
- expect(v '1.17').to be < '1.17.1'
16
- expect(v '1.17').to be < '1.99'
17
- expect(v '1.17').to be < '2.1'
16
+ it{ is_expected.to be > '0' }
17
+ it{ is_expected.to be > '0.1' }
18
+ it{ is_expected.to be > '0.9' }
19
+ it{ is_expected.to be > '1.9' }
20
+ it{ is_expected.to be < '1.17.1' }
21
+ it{ is_expected.to be < '1.99' }
22
+ it{ is_expected.to be < '2.1' }
18
23
  end
19
24
 
20
- it 'should normalize versions' do
21
- variations = %w[1 01 1.0 1.00 1.0.0 1.0.0.0]
22
- variations.each do |a|
23
- variations.each do |b|
24
- expect(v a).to eq(b)
25
+ describe 'normalization' do
26
+ %w[
27
+ 1
28
+ 01
29
+ 1.0
30
+ 1.00
31
+ 1.0.0
32
+ 1.0.0.0
33
+ ].each do |variation|
34
+ it "normalizes #{variation}" do
35
+ expect(v variation).to eq(1)
25
36
  end
26
37
  end
27
38
  end
28
39
 
29
- it 'should convert objects' do
30
- expect(v 1.17).to eq('1.17')
31
- expect(v '1.17').to eq('1.17')
32
- expect(v(v 1.17)).to eq('1.17')
40
+ describe 'conversion' do
41
+ it 'converts Integer' do
42
+ expect(v 117).to eq('117')
43
+ end
44
+
45
+ it 'converts Float' do
46
+ expect(v 1.17).to eq('1.17')
47
+ end
48
+
49
+ it 'converts String' do
50
+ expect(v '1.17').to eq('1.17')
51
+ end
52
+
53
+ it 'converts self' do
54
+ expect(v(v 1.17)).to eq('1.17')
55
+ end
33
56
  end
34
57
  end
@@ -1,20 +1,23 @@
1
- $LOAD_PATH.unshift File.expand_path('../../../lib', __FILE__)
2
- require 'rspec'
1
+ require 'spec_helper'
3
2
  require 'image_optim/bin_resolver'
4
-
5
- def with_env(key, value)
6
- saved, ENV[key] = ENV[key], value
7
- yield
8
- ensure
9
- ENV[key] = saved
10
- end
3
+ require 'image_optim/cmd'
11
4
 
12
5
  describe ImageOptim::BinResolver do
13
- BinResolver = ImageOptim::BinResolver
14
- Bin = BinResolver::Bin
15
- SimpleVersion = BinResolver::SimpleVersion
6
+ def with_env(key, value)
7
+ saved, ENV[key] = ENV[key], value
8
+ yield
9
+ ensure
10
+ ENV[key] = saved
11
+ end
16
12
 
17
- let(:image_optim){ double(:image_optim, :verbose => false) }
13
+ before do
14
+ stub_const('BinResolver', ImageOptim::BinResolver)
15
+ stub_const('Bin', BinResolver::Bin)
16
+ stub_const('SimpleVersion', BinResolver::SimpleVersion)
17
+ stub_const('Cmd', ImageOptim::Cmd)
18
+ end
19
+
20
+ let(:image_optim){ double(:image_optim, :verbose => false, :pack => false) }
18
21
  let(:resolver){ BinResolver.new(image_optim) }
19
22
 
20
23
  describe :full_path do
@@ -23,25 +26,39 @@ describe ImageOptim::BinResolver do
23
26
  end
24
27
 
25
28
  def command_v(name)
26
- path = `sh -c 'command -v #{name}' 2> /dev/null`.strip
29
+ path = Cmd.capture("sh -c 'command -v #{name}' 2> /dev/null").strip
27
30
  path unless path.empty?
28
31
  end
29
32
 
30
- it 'should find binary in path' do
33
+ it 'finds binary in path' do
31
34
  with_env 'PATH', 'bin' do
32
35
  expect(full_path('image_optim')).
33
36
  to eq(File.expand_path('bin/image_optim'))
34
37
  end
35
38
  end
36
39
 
37
- it 'should find bin in vendor' do
40
+ it 'finds bin in vendor' do
38
41
  with_env 'PATH', nil do
39
42
  expect(full_path('jpegrescan')).
40
43
  to eq(File.expand_path('vendor/jpegrescan'))
41
44
  end
42
45
  end
43
46
 
44
- it 'should work with different path separator' do
47
+ it 'finds bin in pack' do
48
+ allow(image_optim).to receive(:pack).and_return(true)
49
+ stub_const('ImageOptim::Pack', Class.new do
50
+ def self.path
51
+ 'script'
52
+ end
53
+ end)
54
+
55
+ with_env 'PATH', nil do
56
+ expect(full_path('update_worker_options_in_readme')).
57
+ to eq(File.expand_path('script/update_worker_options_in_readme'))
58
+ end
59
+ end
60
+
61
+ it 'works with different path separator' do
45
62
  stub_const('File::PATH_SEPARATOR', 'O_o')
46
63
  with_env 'PATH', 'bin' do
47
64
  expect(full_path('image_optim')).
@@ -49,20 +66,37 @@ describe ImageOptim::BinResolver do
49
66
  end
50
67
  end
51
68
 
52
- it 'should return nil on failure' do
69
+ it 'returns nil on failure' do
53
70
  with_env 'PATH', 'lib' do
54
71
  expect(full_path('image_optim')).to be_nil
55
72
  end
56
73
  end
57
74
 
58
- %w[ls sh which bash image_optim should_not_exist].each do |name|
59
- it "should return same path as `command -v` for #{name}" do
75
+ %w[ls sh which bash image_optim does_not_exist].each do |name|
76
+ it "returns same path as `command -v` for #{name}" do
60
77
  expect(full_path(name)).to eq(command_v(name))
61
78
  end
62
79
  end
63
80
  end
64
81
 
65
- it 'should resolve bin in path' do
82
+ it 'combines path in order dir:pack:path:vendor' do
83
+ allow(image_optim).to receive(:pack).and_return(true)
84
+ stub_const('ImageOptim::Pack', Class.new do
85
+ def self.path
86
+ 'pack_path'
87
+ end
88
+ end)
89
+ allow(resolver).to receive(:dir).and_return('temp_dir')
90
+
91
+ expect(resolver.env_path).to eq([
92
+ 'temp_dir',
93
+ 'pack_path',
94
+ ENV['PATH'],
95
+ BinResolver::VENDOR_PATH,
96
+ ].join(':'))
97
+ end
98
+
99
+ it 'resolves bin in path and returns instance of Bin' do
66
100
  with_env 'LS_BIN', nil do
67
101
  expect(FSPath).not_to receive(:temp_dir)
68
102
  expect(resolver).to receive(:full_path).with(:ls).and_return('/bin/ls')
@@ -71,7 +105,7 @@ describe ImageOptim::BinResolver do
71
105
  expect(bin).to receive(:check!).exactly(5).times
72
106
 
73
107
  5.times do
74
- resolver.resolve!(:ls)
108
+ expect(resolver.resolve!(:ls)).to eq(bin)
75
109
  end
76
110
  expect(resolver.env_path).to eq([
77
111
  ENV['PATH'],
@@ -80,7 +114,7 @@ describe ImageOptim::BinResolver do
80
114
  end
81
115
  end
82
116
 
83
- it 'should raise on failure to resolve bin' do
117
+ it 'raises on failure to resolve bin' do
84
118
  with_env 'LS_BIN', nil do
85
119
  expect(FSPath).not_to receive(:temp_dir)
86
120
  expect(resolver).to receive(:full_path).with(:ls).and_return(nil)
@@ -98,7 +132,7 @@ describe ImageOptim::BinResolver do
98
132
  end
99
133
  end
100
134
 
101
- it 'should resolve bin specified in ENV' do
135
+ it 'resolves bin specified in ENV' do
102
136
  path = 'bin/image_optim'
103
137
  with_env 'IMAGE_OPTIM_BIN', path do
104
138
  tmpdir = double(:tmpdir, :to_str => 'tmpdir')
@@ -137,11 +171,11 @@ describe ImageOptim::BinResolver do
137
171
  end
138
172
 
139
173
  {
140
- 'some/path/should_not_exist_bin' => 'doesn\'t exist',
174
+ 'some/path/does/not/exist' => 'doesn\'t exist',
141
175
  '.' => 'is not a file',
142
176
  __FILE__ => 'is not executable',
143
177
  }.each do |path, error_message|
144
- it "should raise when bin specified in ENV #{error_message}" do
178
+ it "raises when bin specified in ENV #{error_message}" do
145
179
  with_env 'IMAGE_OPTIM_BIN', path do
146
180
  expect(FSPath).not_to receive(:temp_dir)
147
181
  expect(resolver).not_to receive(:at_exit)
@@ -159,7 +193,7 @@ describe ImageOptim::BinResolver do
159
193
  end
160
194
  end
161
195
 
162
- it 'should resolve bin only once, but check every time' do
196
+ it 'resolves bin only once, but checks every time' do
163
197
  with_env 'LS_BIN', nil do
164
198
  expect(resolver).to receive(:full_path).once.with(:ls) do
165
199
  sleep 0.1
@@ -182,7 +216,7 @@ describe ImageOptim::BinResolver do
182
216
  end
183
217
  end
184
218
 
185
- it 'should raise if did not got bin version' do
219
+ it 'raises if did not got bin version' do
186
220
  bin = Bin.new(:pngcrush, '/bin/pngcrush')
187
221
  allow(bin).to receive(:version).and_return(nil)
188
222
 
@@ -193,7 +227,7 @@ describe ImageOptim::BinResolver do
193
227
  end
194
228
  end
195
229
 
196
- it 'should raise on detection of problematic version' do
230
+ it 'raises on detection of problematic version' do
197
231
  bin = Bin.new(:pngcrush, '/bin/pngcrush')
198
232
  allow(bin).to receive(:version).and_return(SimpleVersion.new('1.7.60'))
199
233
 
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+ require 'image_optim/cmd'
3
+
4
+ describe ImageOptim::Cmd do
5
+ before do
6
+ stub_const('Cmd', ImageOptim::Cmd)
7
+ end
8
+
9
+ def expect_int_exception(&block)
10
+ expect(&block).to raise_error(SignalException) do |error|
11
+ expect(error.message.to_s).to match(/INT|#{Signal.list['INT']}/)
12
+ end
13
+ end
14
+
15
+ describe :run do
16
+ it 'calls system and returns result' do
17
+ status = double
18
+ expect(Cmd).to receive(:system).with('cmd', 'arg').and_return(status)
19
+ allow(Cmd).to receive(:check_status!)
20
+ expect(Cmd.run('cmd', 'arg')).to eq(status)
21
+ end
22
+
23
+ it 'returns process success status' do
24
+ expect(Cmd.run('sh -c exit\ 0')).to eq(true)
25
+ expect($CHILD_STATUS.exitstatus).to eq(0)
26
+
27
+ expect(Cmd.run('sh -c exit\ 1')).to eq(false)
28
+ expect($CHILD_STATUS.exitstatus).to eq(1)
29
+
30
+ expect(Cmd.run('sh -c exit\ 66')).to eq(false)
31
+ expect($CHILD_STATUS.exitstatus).to eq(66)
32
+ end
33
+
34
+ it 'raises SignalException if process terminates after signal' do
35
+ expect_int_exception do
36
+ Cmd.run('kill -s INT $$')
37
+ end
38
+ end
39
+ end
40
+
41
+ describe :capture do
42
+ it 'calls ` and returns result' do
43
+ output = double
44
+ expect(Cmd).to receive(:`).with('cmd arg arg+').and_return(output)
45
+ allow(Cmd).to receive(:check_status!)
46
+ expect(Cmd.capture('cmd arg arg+')).to eq(output)
47
+ end
48
+
49
+ it 'returns output' do
50
+ expect(Cmd.capture('echo test')).to eq("test\n")
51
+ expect($CHILD_STATUS.exitstatus).to eq(0)
52
+
53
+ expect(Cmd.capture('printf more; sh -c exit\ 1')).to eq('more')
54
+ expect($CHILD_STATUS.exitstatus).to eq(1)
55
+
56
+ expect(Cmd.capture('sh -c exit\ 66')).to eq('')
57
+ expect($CHILD_STATUS.exitstatus).to eq(66)
58
+ end
59
+
60
+ it 'raises SignalException if process terminates after signal' do
61
+ expect_int_exception do
62
+ Cmd.capture('kill -s INT $$')
63
+ end
64
+ end
65
+ end
66
+ end