image_optim 0.17.1 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
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