mini_magick 3.8.0 → 4.0.0.rc

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mini_gmagick.rb +2 -1
  3. data/lib/mini_magick/configuration.rb +136 -0
  4. data/lib/mini_magick/image/info.rb +104 -0
  5. data/lib/mini_magick/image.rb +384 -345
  6. data/lib/mini_magick/logger.rb +40 -0
  7. data/lib/mini_magick/shell.rb +46 -0
  8. data/lib/mini_magick/tool/animate.rb +14 -0
  9. data/lib/mini_magick/tool/compare.rb +14 -0
  10. data/lib/mini_magick/tool/composite.rb +14 -0
  11. data/lib/mini_magick/tool/conjure.rb +14 -0
  12. data/lib/mini_magick/tool/convert.rb +14 -0
  13. data/lib/mini_magick/tool/display.rb +14 -0
  14. data/lib/mini_magick/tool/identify.rb +14 -0
  15. data/lib/mini_magick/tool/import.rb +14 -0
  16. data/lib/mini_magick/tool/mogrify.rb +14 -0
  17. data/lib/mini_magick/tool/montage.rb +14 -0
  18. data/lib/mini_magick/tool/stream.rb +14 -0
  19. data/lib/mini_magick/tool.rb +233 -0
  20. data/lib/mini_magick/utilities.rb +25 -26
  21. data/lib/mini_magick/version.rb +15 -1
  22. data/lib/mini_magick.rb +43 -79
  23. data/spec/fixtures/animation.gif +0 -0
  24. data/spec/fixtures/default.jpg +0 -0
  25. data/spec/fixtures/exif.jpg +0 -0
  26. data/spec/fixtures/image.psd +0 -0
  27. data/spec/fixtures/not_an_image.rb +1 -0
  28. data/spec/lib/mini_magick/configuration_spec.rb +66 -0
  29. data/spec/lib/mini_magick/image_spec.rb +407 -0
  30. data/spec/lib/mini_magick/shell_spec.rb +66 -0
  31. data/spec/lib/mini_magick/tool_spec.rb +90 -0
  32. data/spec/lib/mini_magick/utilities_spec.rb +17 -0
  33. data/spec/lib/mini_magick_spec.rb +39 -0
  34. data/spec/spec_helper.rb +21 -0
  35. data/spec/support/helpers.rb +37 -0
  36. metadata +52 -54
  37. data/lib/mini_magick/command_builder.rb +0 -110
  38. data/lib/mini_magick/errors.rb +0 -4
data/lib/mini_magick.rb CHANGED
@@ -1,89 +1,53 @@
1
- require 'tempfile'
2
- require 'subexec'
3
- require 'stringio'
4
- require 'pathname'
5
- require 'shellwords'
6
- require 'mini_magick/command_builder'
7
- require 'mini_magick/errors'
1
+ require 'mini_magick/configuration'
2
+ require 'mini_magick/tool'
8
3
  require 'mini_magick/image'
9
- require 'mini_magick/utilities'
10
4
 
11
5
  module MiniMagick
12
- @validate_on_create = true
13
- @validate_on_write = true
14
6
 
15
- class << self
16
- attr_accessor :processor
17
- attr_accessor :processor_path
18
- attr_accessor :timeout
19
- attr_accessor :validate_on_create
20
- attr_accessor :validate_on_write
21
-
22
- ##
23
- # Tries to detect the current processor based if any of the processors exist.
24
- # Mogrify have precedence over gm by default.
25
- #
26
- # === Returns
27
- # * [String] The detected procesor
28
- def choose_processor
29
- self.processor = if MiniMagick::Utilities.which('mogrify')
30
- :mogrify
31
- elsif MiniMagick::Utilities.which('gm')
32
- :gm
33
- else
34
- nil
35
- end
36
- end
37
-
38
- ##
39
- # Discovers the imagemagick version based on mogrify's output.
40
- #
41
- # === Returns
42
- # * The imagemagick version
43
- def image_magick_version
44
- @@version ||= Gem::Version.create(`mogrify --version`.split(' ')[2].split('-').first)
45
- end
46
-
47
- ##
48
- # The minimum allowed imagemagick version
49
- #
50
- # === Returns
51
- # * The minimum imagemagick version
52
- def minimum_image_magick_version
53
- @@minimum_version ||= Gem::Version.create('6.6.3')
54
- end
7
+ extend MiniMagick::Configuration
8
+
9
+ ##
10
+ # You might want to execute only certain blocks of processing with a
11
+ # different CLI, because for example that CLI does that particular thing
12
+ # faster. After the block CLI resets to its previous value.
13
+ #
14
+ # @example
15
+ # MiniMagick.with_cli :graphicsmagick do
16
+ # # operations that are better done with GraphicsMagick
17
+ # end
18
+ def self.with_cli(cli)
19
+ old_cli = self.cli
20
+ self.cli = cli
21
+ yield
22
+ self.cli = old_cli
23
+ end
55
24
 
56
- ##
57
- # Checks whether the imagemagick's version is valid
58
- #
59
- # === Returns
60
- # * [Boolean]
61
- def valid_version_installed?
62
- image_magick_version >= minimum_image_magick_version
63
- end
25
+ ##
26
+ # Checks whether the CLI used is ImageMagick.
27
+ #
28
+ # @return [Boolean]
29
+ def self.imagemagick?
30
+ cli == :imagemagick
31
+ end
64
32
 
65
- ##
66
- # Picks the right processor if it isn't set and returns whether it's mogrify or not.
67
- #
68
- # === Returns
69
- # * [Boolean]
70
- def mogrify?
71
- choose_processor if processor.nil?
33
+ ##
34
+ # Checks whether the CLI used is GraphicsMagick.
35
+ #
36
+ # @return [Boolean]
37
+ def self.graphicsmagick?
38
+ cli == :graphicsmagick
39
+ end
72
40
 
73
- return processor.to_s.downcase.to_sym == :mogrify unless processor.nil?
74
- false
75
- end
41
+ ##
42
+ # Returns ImageMagick's/GraphicsMagick's version.
43
+ #
44
+ # @return [String]
45
+ def self.cli_version
46
+ output = MiniMagick::Tool::Identify.new(&:version)
47
+ output[/\d+\.\d+\.\d+(-\d+)?/]
48
+ end
76
49
 
77
- ##
78
- # Picks the right processor if it isn't set and returns whether it's graphicsmagick or not.
79
- #
80
- # === Returns
81
- # * [Boolean]
82
- def gm?
83
- choose_processor if processor.nil?
50
+ class Error < RuntimeError; end
51
+ class Invalid < StandardError; end
84
52
 
85
- return processor.to_s.downcase.to_sym == :gm unless processor.nil?
86
- false
87
- end
88
- end
89
53
  end
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1 @@
1
+ expect(__FILE__).not_to be_an_image
@@ -0,0 +1,66 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe MiniMagick::Configuration do
4
+ subject { Object.new.extend(MiniMagick::Configuration) }
5
+
6
+ describe "#configure" do
7
+ it "yields self" do
8
+ expect { |b| subject.configure(&b) }
9
+ .to yield_with_args(subject)
10
+ end
11
+ end
12
+
13
+ describe "#cli" do
14
+ it "can be assigned" do
15
+ subject.cli = :imagemagick
16
+ expect(subject.cli).to eq :imagemagick
17
+ end
18
+
19
+ it "returns :imagemagick if #processor is mogrify" do
20
+ allow(subject).to receive(:processor).and_return("mogrify")
21
+ expect(subject.cli).to eq :imagemagick
22
+ end
23
+
24
+ it "returns :graphicsmagick if #processor is gm" do
25
+ allow(subject).to receive(:processor).and_return("gm")
26
+ expect(subject.cli).to eq :graphicsmagick
27
+ end
28
+
29
+ it "returns nil of #processor is nil" do
30
+ allow(subject).to receive(:processor).and_return(nil)
31
+ expect(subject.cli).to eq nil
32
+ end
33
+ end
34
+
35
+ describe "#cli=" do
36
+ it "raises an error when set to an invalid value" do
37
+ expect { subject.cli = :grapicsmagick }
38
+ .to raise_error(ArgumentError)
39
+ end
40
+ end
41
+
42
+ describe "#processor" do
43
+ it "assigns :mogrify by default" do
44
+ expect(subject.processor).to eq "mogrify"
45
+ end
46
+
47
+ it "assigns :gm if ImageMagick is not available" do
48
+ allow(MiniMagick::Utilities).to receive(:which).with("mogrify").and_return(nil)
49
+ allow(MiniMagick::Utilities).to receive(:which).with("gm").and_return(true)
50
+ expect(subject.processor).to eq "gm"
51
+ end
52
+
53
+ it "returns nil if neither ImageMagick nor GraphicsMagick are available" do
54
+ allow(MiniMagick::Utilities).to receive(:which).with("mogrify").and_return(nil)
55
+ allow(MiniMagick::Utilities).to receive(:which).with("gm").and_return(nil)
56
+ expect(subject.processor).to eq nil
57
+ end
58
+ end
59
+
60
+ describe "#processor=" do
61
+ it "raises an error when set to an invalid value" do
62
+ expect { subject.processor = "mogrfy" }
63
+ .to raise_error(ArgumentError)
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,407 @@
1
+ require "spec_helper"
2
+ require "pathname"
3
+ require "tempfile"
4
+ require "fileutils"
5
+ require "stringio"
6
+
7
+ ["ImageMagick", "GraphicsMagick"].each do |cli|
8
+ RSpec.context "With #{cli}", cli: cli.downcase.to_sym do
9
+ describe MiniMagick::Image do
10
+ subject { described_class.open(image_path) }
11
+
12
+ describe ".read" do
13
+ it "reads image from String" do
14
+ string = File.binread(image_path)
15
+ image = described_class.read(string)
16
+ expect(image).to be_valid
17
+ end
18
+
19
+ it "reads image from StringIO" do
20
+ stringio = StringIO.new(File.binread(image_path))
21
+ image = described_class.read(stringio)
22
+ expect(image).to be_valid
23
+ end
24
+
25
+ it "reads image from tempfile" do
26
+ tempfile = Tempfile.open('magick')
27
+ FileUtils.cp image_path, tempfile.path
28
+ image = described_class.read(tempfile)
29
+ expect(image).to be_valid
30
+ end
31
+ end
32
+
33
+ describe ".import_pixels" do
34
+ let(:dimensions) { [325, 200] }
35
+ let(:depth) { 16 } # 16 bits (2 bytes) per pixel
36
+ let(:map) { 'gray' }
37
+ let(:pixels) { Array.new(dimensions.inject(:*)) { |i| i } }
38
+ let(:blob) { pixels.pack('S*') } # unsigned short, native byte order
39
+
40
+ it "can import pixels with default format" do
41
+ image = described_class.import_pixels(blob, *dimensions, depth, map)
42
+
43
+ expect(image).to be_valid
44
+ expect(image.type).to eq 'PNG'
45
+ expect(image.dimensions).to eq dimensions
46
+ end
47
+
48
+ it "can import pixels with custom format" do
49
+ image = described_class.import_pixels(blob, *dimensions, depth, map, 'jpeg')
50
+
51
+ expect(image).to be_valid
52
+ expect(image.type).to eq 'JPEG'
53
+ expect(image.dimensions).to eq dimensions
54
+ end
55
+ end
56
+
57
+ describe ".open" do
58
+ it "makes a copy of the image" do
59
+ image = described_class.open(image_path)
60
+ expect(image.path).not_to eq image_path
61
+ expect(image).to be_valid
62
+ end
63
+
64
+ it "accepts a Pathname" do
65
+ image = described_class.open(Pathname(image_path))
66
+ expect(image).to be_valid
67
+ end
68
+
69
+ it "loads a remote image" do
70
+ begin
71
+ image = described_class.open(image_url)
72
+ expect(image).to be_valid
73
+ rescue SocketError
74
+ end
75
+ end
76
+
77
+ it "validates the image" do
78
+ expect { described_class.open(image_path(:not)) }
79
+ .to raise_error(MiniMagick::Invalid)
80
+ end
81
+ end
82
+
83
+ describe ".create" do
84
+ def create(path = image_path)
85
+ described_class.create do |f|
86
+ f.write(File.binread(path))
87
+ end
88
+ end
89
+
90
+ it "creates an image" do
91
+ image = create
92
+ expect(File.exists?(image.path)).to eq true
93
+ end
94
+
95
+ it "validates the image if validation is set" do
96
+ allow(MiniMagick).to receive(:validate_on_create).and_return(true)
97
+ expect { create(image_path(:not)) }
98
+ .to raise_error(MiniMagick::Invalid)
99
+ end
100
+
101
+ it "doesn't validate image if validation is disabled" do
102
+ allow(MiniMagick).to receive(:validate_on_create).and_return(false)
103
+ expect { create(image_path(:not)) }
104
+ .not_to raise_error
105
+ end
106
+ end
107
+
108
+ describe "#initialize" do
109
+ it "initializes a new image" do
110
+ image = described_class.new(image_path)
111
+ expect(image).to be_valid
112
+ end
113
+
114
+ it "accepts a block which it passes on to #combine_options" do
115
+ image = described_class.new(subject.path) do |b|
116
+ b.resize "100x100!"
117
+ end
118
+ expect(image.dimensions).to eq [100, 100]
119
+ end
120
+ end
121
+
122
+ describe "#format" do
123
+ subject { described_class.open(image_path(:jpg)) }
124
+
125
+ it "changes the format of the photo" do
126
+ expect { subject.format("png") }
127
+ .to change { subject.type }
128
+ end
129
+
130
+ it "reformats an image with a given extension" do
131
+ expect { subject.format('png') }
132
+ .to change { File.extname(subject.path) }.to ".png"
133
+ end
134
+
135
+ it "creates the file with new extension" do
136
+ subject.format('png')
137
+ expect(File.exist?(subject.path)).to eq true
138
+ end
139
+
140
+ it "accepts a block of additional commands" do
141
+ expect {
142
+ subject.format("png") do |b|
143
+ b.resize("100x100!")
144
+ end
145
+ }.to change { subject.dimensions }.to [100, 100]
146
+ end
147
+
148
+ it "works without an extension" do
149
+ subject = described_class.open(image_path(:without_extension))
150
+ expect { subject.format("png") }
151
+ .to change { File.extname(subject.path) }.from("").to(".png")
152
+ end
153
+
154
+ it "deletes the previous tempfile" do
155
+ old_path = subject.path.dup
156
+ subject.format('png')
157
+ expect(File.exist?(old_path)).to eq false
158
+ end
159
+
160
+ it "doesn't delete itself when formatted to the same format" do
161
+ subject.format(subject.type.downcase)
162
+ expect(File.exists?(subject.path)).to eq true
163
+ end
164
+
165
+ it "reformats multi-image formats to multiple images" do
166
+ subject = described_class.open(image_path(:animation))
167
+ subject.format('jpg', nil)
168
+ expect(Dir[subject.path.sub('.', '*.')]).not_to be_empty
169
+ end
170
+
171
+ it "reformats multi-image formats to a single image" do
172
+ subject = described_class.open(image_path(:animation))
173
+ subject.format('jpg')
174
+ expect(subject).to be_valid
175
+ end
176
+
177
+ it "returns self" do
178
+ expect(subject.format('png')).to eq subject
179
+ end
180
+ end
181
+
182
+ describe "#write" do
183
+ it "writes the image" do
184
+ output_path = random_path("test output")
185
+ subject.write(output_path)
186
+ expect(described_class.new(output_path)).to be_valid
187
+ end
188
+
189
+ it "writes an image with stream" do
190
+ output_stream = StringIO.new
191
+ subject.write(output_stream)
192
+ expect(described_class.read(output_stream.string)).to be_valid
193
+ end
194
+
195
+ it "writes layers" do
196
+ output_path = random_path(["", ".#{subject.type.downcase}"])
197
+ subject = described_class.new(image_path(:gif))
198
+ subject.frames.first.write(output_path)
199
+ expect(described_class.new(output_path)).to be_valid
200
+ end
201
+
202
+ it "accepts a Pathname" do
203
+ output_path = Pathname(random_path)
204
+ subject.write(output_path)
205
+ expect(described_class.new(output_path.to_s)).to be_valid
206
+ end
207
+ end
208
+
209
+ describe "#valid?" do
210
+ it "returns true when image is valid" do
211
+ image = described_class.new(image_path)
212
+ expect(image).to be_valid
213
+ end
214
+
215
+ it "returns false when image is not valid" do
216
+ image = described_class.new(image_path(:not))
217
+ expect(image).not_to be_valid
218
+ end
219
+ end
220
+
221
+ describe "#[]" do
222
+ it "inspects image meta info" do
223
+ expect(subject[:width]).to be_a(Fixnum)
224
+ expect(subject[:height]).to be_a(Fixnum)
225
+ expect(subject[:dimensions]).to all(be_a(Fixnum))
226
+ expect(subject[:colorspace]).to be_a(String)
227
+ expect(subject[:format]).to match(/[A-Z]/)
228
+ end
229
+
230
+ it "supports string keys" do
231
+ expect(subject["width"]).to be_a(Fixnum)
232
+ expect(subject["height"]).to be_a(Fixnum)
233
+ expect(subject["dimensions"]).to all(be_a(Fixnum))
234
+ expect(subject["colorspace"]).to be_a(String)
235
+ expect(subject["format"]).to match(/[A-Z]/)
236
+ end
237
+
238
+ it "reads exif" do
239
+ subject = described_class.new(image_path(:exif))
240
+ expect(subject["EXIF:ColorSpace"]).to eq "1"
241
+ end
242
+
243
+ it "passes unknown values directly to -format" do
244
+ expect(subject["%w %h"].split.map(&:to_i)).to eq [subject[:width], subject[:height]]
245
+ end
246
+ end
247
+
248
+ it "has attributes" do
249
+ expect(subject.type).to match(/^[A-Z]+$/)
250
+ expect(subject.mime_type).to match(/^image\/[a-z]+$/)
251
+ expect(subject.width).to be_a(Fixnum).and be_nonzero
252
+ expect(subject.height).to be_a(Fixnum).and be_nonzero
253
+ expect(subject.dimensions).to all(be_a(Fixnum))
254
+ expect(subject.size).to be_a(Fixnum).and be_nonzero
255
+ expect(subject.colorspace).to be_a(String)
256
+ expect(subject.resolution).to all(be_a(Fixnum))
257
+ end
258
+
259
+ describe "#exif" do
260
+ subject { described_class.new(image_path(:exif)) }
261
+
262
+ it "returns a hash of EXIF data" do
263
+ expect(subject.exif["DateTimeOriginal"]).to be_a(String)
264
+ end
265
+ end
266
+
267
+ describe "#resolution" do
268
+ it "accepts units", skip_cli: :graphicsmagick do
269
+ expect(subject.resolution("PixelsPerCentimeter"))
270
+ .not_to eq subject.resolution("PixelsPerInch")
271
+ end
272
+ end
273
+
274
+ describe "#mime_type" do
275
+ it "returns the correct mime type" do
276
+ jpg = described_class.new(image_path(:jpg))
277
+ expect(jpg.mime_type).to eq 'image/jpeg'
278
+ end
279
+ end
280
+
281
+ describe "#layers" do
282
+ it "returns a list of images" do
283
+ expect(subject.layers).to all(be_a(MiniMagick::Image))
284
+ expect(subject.layers.first).to be_valid
285
+ end
286
+
287
+ it "returns multiple images for GIFs, PDFs and PSDs" do
288
+ gif = described_class.new(image_path(:gif))
289
+ psd = described_class.new(image_path(:psd))
290
+
291
+ expect(gif.frames.count).to be > 1
292
+ expect(psd.layers.count).to be > 1 unless MiniMagick.graphicsmagick?
293
+ end
294
+
295
+ it "returns one image for other formats" do
296
+ jpg = described_class.new(image_path(:jpg))
297
+
298
+ expect(jpg.layers.count).to eq 1
299
+ end
300
+ end
301
+
302
+ describe "#method_missing" do
303
+ it "executes the command correctly" do
304
+ expect { subject.resize '20x30!' }
305
+ .to change { subject.dimensions }.to [20, 30]
306
+ end
307
+
308
+ it "fails with a correct NoMethodError" do
309
+ expect { subject.foo }
310
+ .to raise_error(NoMethodError, /MiniMagick::Image/)
311
+ end
312
+
313
+ it "returns self" do
314
+ expect(subject.resize('20x30!')).to eq subject
315
+ end
316
+ end
317
+
318
+ describe "#combine_options" do
319
+ it "chains multiple options and executes them in one command" do
320
+ expect {
321
+ subject.combine_options { |c| c.resize '20x30!' }
322
+ }.to change { subject.dimensions }.to [20, 30]
323
+ end
324
+
325
+ it "doesn't allow calling of #format" do
326
+ expect { subject.combine_options { |c| c.format("png") } }
327
+ .to raise_error(NoMethodError)
328
+ end
329
+
330
+ it "returns self" do
331
+ expect(subject.combine_options {}).to eq subject
332
+ end
333
+ end
334
+
335
+ describe "#composite" do
336
+ let(:other_image) { described_class.open(image_path) }
337
+ let(:mask) { described_class.open(image_path) }
338
+
339
+ it "creates a composite of two images" do
340
+ image = subject.composite(other_image)
341
+ expect(image).to be_valid
342
+ end
343
+
344
+ it "creates a composite of two images with mask" do
345
+ image = subject.composite(other_image, 'jpg', mask)
346
+ expect(image).to be_valid
347
+ end
348
+
349
+ it "yields an optional block" do
350
+ expect { |b| subject.composite(other_image, &b) }
351
+ .to yield_with_args(an_instance_of(MiniMagick::Tool::Composite))
352
+ end
353
+
354
+ it "makes the composited image with the provided extension" do
355
+ result = subject.composite(other_image, 'png')
356
+ expect(result.path).to end_with ".png"
357
+
358
+ result = subject.composite(other_image)
359
+ expect(result.path).to end_with ".jpg"
360
+ end
361
+ end
362
+
363
+ describe "#collapse!" do
364
+ subject { described_class.open(image_path(:animation)) }
365
+
366
+ it "collapses the image to one frame" do
367
+ subject.collapse!
368
+ expect(subject.identify.lines.count).to eq 1
369
+ end
370
+
371
+ it "keeps the extension" do
372
+ expect { subject.collapse! }
373
+ .not_to change { subject.type }
374
+ end
375
+
376
+ it "clears the info" do
377
+ expect { subject.collapse! }
378
+ .to change { subject.size }
379
+ end
380
+
381
+ it "returns self" do
382
+ expect(subject.collapse!).to eq subject
383
+ end
384
+ end
385
+
386
+ describe "#identify" do
387
+ it "returns the output of identify" do
388
+ expect(subject.identify).to match(subject.type)
389
+ end
390
+
391
+ it "yields an optional block" do
392
+ output = subject.identify do |b|
393
+ b.verbose
394
+ end
395
+ expect(output).to match("Format:")
396
+ end
397
+ end
398
+
399
+ describe "#run_command" do
400
+ it "runs the given command" do
401
+ output = subject.run_command("identify", "-format", "%w", subject.path)
402
+ expect(output).to eq subject.width.to_s
403
+ end
404
+ end
405
+ end
406
+ end
407
+ end
@@ -0,0 +1,66 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe MiniMagick::Shell do
4
+ subject { described_class.new }
5
+
6
+ describe "#run" do
7
+ it "calls #execute with the command" do
8
+ expect(subject).to receive(:execute).and_call_original
9
+ subject.run(%W[identify #{image_path}])
10
+ end
11
+
12
+ it "returns stdout" do
13
+ allow(subject).to receive(:execute).and_return(["stdout", "stderr", 0])
14
+ output = subject.run(%W[foo])
15
+ expect(output).to eq "stdout"
16
+ end
17
+
18
+ it "uses stderr for error messages" do
19
+ allow(subject).to receive(:execute).and_return(["", "stderr", 1])
20
+ expect { subject.run(%W[foo]) }
21
+ .to raise_error(MiniMagick::Error, /`foo`.*stderr/m)
22
+ end
23
+
24
+ it "raises an error when executable wasn't found" do
25
+ allow(subject).to receive(:execute).and_return(["", "not found", 127])
26
+ expect { subject.run(%W[foo]) }
27
+ .to raise_error(MiniMagick::Error, /not found/)
28
+ end
29
+
30
+ it "raises errors only in whiny mode" do
31
+ subject = described_class.new(false)
32
+ allow(subject).to receive(:execute).and_return(["stdout", "", 127])
33
+ expect(subject.run(%W[foo])).to eq "stdout"
34
+ end
35
+ end
36
+
37
+ describe "#execute" do
38
+ it "executes the command in the shell" do
39
+ stdout, * = subject.execute(%W[identify #{image_path(:gif)}])
40
+ expect(stdout).to match("GIF")
41
+ end
42
+
43
+ it "timeouts afer a period of time" do
44
+ allow(MiniMagick).to receive(:timeout).and_return(0.001)
45
+ expect { subject.execute(%W[identify -format %[EXIF:*] #{image_path(:exif)}]) }
46
+ .to raise_error(Timeout::Error)
47
+ end
48
+
49
+ it "logs the command and execution time in debug mode" do
50
+ allow(MiniMagick).to receive(:debug).and_return(true)
51
+ expect { subject.execute(%W[identify #{image_path(:gif)}]) }.
52
+ to output(/\[\d+.\d+s\] identify #{image_path(:gif)}/).to_stdout
53
+ end
54
+
55
+ it "returns an appropriate response when command wasn't found" do
56
+ stdout, stderr, code = subject.execute(%W[unexisting command])
57
+ expect(code).to eq 127
58
+ expect(stderr).to match(/not found/)
59
+ end
60
+
61
+ it "doesn't break on spaces" do
62
+ stdout, * = subject.execute(["identify", "-format", "%w %h", image_path])
63
+ expect(stdout).to match(/\d+ \d+/)
64
+ end
65
+ end
66
+ end