headless 2.1.0 → 2.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8dc1c06f85049924c12fc40b4558040fd514c01a
4
- data.tar.gz: a5e904086bdccf72798574e2450ce76b64fda0a7
3
+ metadata.gz: d30b463017cd61d3b19e47c94a04e9aa4061acc7
4
+ data.tar.gz: d8b07cc50c12a80ad547a8214a530cffa55db8f0
5
5
  SHA512:
6
- metadata.gz: d6ad825721817e789fde8d9786bac8fec26ef097e5fb4af74f8c685a9895dfa3b5850161f3bf8f809b5024a7827c08f35dfd131d843e2f0cc71b274b4197ddea
7
- data.tar.gz: af0c4aea4a57b14f56868315a1874bf28a09e7ab8a4a91355d0b864e09e939794aa9eedcf9ced54692c79dc428ae5a30003ea43afaa097edaaf75b06ee85fd5f
6
+ metadata.gz: fadb8ac4e38bb5ce3dc9f2f8245d3a13055e5d384d8aafb6ef87aecfaed732505d0017054a0d7d84c58c9854456aa01015bd83e3fdfb0e047a0ef40217a13dab
7
+ data.tar.gz: 5574ded55ca457df6835da69994911da9be288fb1ab1f943e0ff3215d52d1618596876f042325b08c9e1ecc7d299de0f4542a001f654f1b81a1180cc86bfe098
data/CHANGELOG CHANGED
@@ -1,3 +1,9 @@
1
+ ## 2.2.0 (2015-07-05)
2
+
3
+ * Allow reuse of displays started by other user (from @marxarelli)
4
+ * Add support for graphicsmagick instead of ImageMagick (from @BlakeMesdag)
5
+ * Wait for Xvfb to finish when destroying it, to avoid creating zombie processes (from @samnissen)
6
+
1
7
  ## 2.1.0 (2015-05-10)
2
8
 
3
9
  * Allow path to video recorder binary to be customized (from @briandamaged)
data/README.md CHANGED
@@ -83,8 +83,13 @@ Headless.new(display: 100, reuse: true, destroy_at_exit: false).start
83
83
  # reap_headless.rb
84
84
  headless = Headless.new(display: 100, reuse: true)
85
85
  headless.destroy
86
+
87
+ # kill_headless_without_waiting.rb
88
+ headless = Headless.new
89
+ headless.destroy_without_sync
86
90
  ```
87
-
91
+
92
+ There's also a different approach that creates a new virtual display for every parallel test process - see [this implementation](https://gist.github.com/rosskevin/5937888) by @rosskevin.
88
93
 
89
94
  ## Cucumber with wkhtmltopdf
90
95
 
@@ -3,7 +3,7 @@ spec = Gem::Specification.new do |s|
3
3
  s.email = 'leonid@shevtsov.me'
4
4
 
5
5
  s.name = 'headless'
6
- s.version = '2.1.0'
6
+ s.version = '2.2.0'
7
7
  s.summary = 'Ruby headless display interface'
8
8
 
9
9
  s.description = <<-EOF
@@ -101,15 +101,24 @@ class Headless
101
101
  end
102
102
 
103
103
  # Switches back from the headless server and terminates the headless session
104
+ # while waiting for Xvfb process to terminate.
104
105
  def destroy
105
106
  stop
106
- CliUtil.kill_process(pid_filename, preserve_pid_file: true)
107
+ CliUtil.kill_process(pid_filename, preserve_pid_file: true, wait: true)
107
108
  end
108
109
 
109
- # Same as destroy, but waits for Xvfb process to terminate
110
+ # Deprecated.
111
+ # Same as destroy.
112
+ # Kept for backward compatibility in June 2015.
110
113
  def destroy_sync
114
+ destroy
115
+ end
116
+
117
+ # Same as the old destroy function -- doesn't wait for Xvfb to die.
118
+ # Can cause zombies: http://stackoverflow.com/a/31003621/1651458
119
+ def destroy_without_sync
111
120
  stop
112
- CliUtil.kill_process(pid_filename, preserve_pid_file: true, wait: true)
121
+ CliUtil.kill_process(pid_filename, preserve_pid_file: true)
113
122
  end
114
123
 
115
124
  # Block syntax:
@@ -133,12 +142,16 @@ class Headless
133
142
 
134
143
  def take_screenshot(file_path, options={})
135
144
  using = options.fetch(:using, :imagemagick)
136
- if using == :imagemagick
145
+ case using
146
+ when :imagemagick
137
147
  CliUtil.ensure_application_exists!('import', "imagemagick is not found on your system. Please install it using sudo apt-get install imagemagick")
138
148
  system "#{CliUtil.path_to('import')} -display localhost:#{display} -window root #{file_path}"
139
- elsif using == :xwd
149
+ when :xwd
140
150
  CliUtil.ensure_application_exists!('xwd', "xwd is not found on your system. Please install it using sudo apt-get install X11-apps")
141
151
  system "#{CliUtil.path_to('xwd')} -display localhost:#{display} -silent -root -out #{file_path}"
152
+ when :graphicsmagick, :gm
153
+ CliUtil.ensure_application_exists!('gm', "graphicsmagick is not found on your system. Please install it.")
154
+ system "#{CliUtil.path_to('gm')} import -display localhost:#{display} -window root #{file_path}"
142
155
  else
143
156
  raise Headless::Exception.new('Unknown :using option value')
144
157
  end
@@ -154,12 +167,9 @@ private
154
167
  def pick_available_display(display_set, can_reuse)
155
168
  display_set.each do |display_number|
156
169
  @display = display_number
157
- begin
158
- return true if xvfb_running? && can_reuse
159
- return true if !xvfb_running? && launch_xvfb
160
- rescue Errno::EPERM # display not accessible
161
- next
162
- end
170
+
171
+ return true if xvfb_running? && can_reuse && (xvfb_mine? || !@autopick_display)
172
+ return true if !xvfb_running? && launch_xvfb
163
173
  end
164
174
  raise Headless::Exception.new("Could not find an available display")
165
175
  end
@@ -192,8 +202,12 @@ private
192
202
  end while !xvfb_running?
193
203
  end
194
204
 
205
+ def xvfb_mine?
206
+ CliUtil.process_mine?(read_xvfb_pid)
207
+ end
208
+
195
209
  def xvfb_running?
196
- !!read_xvfb_pid
210
+ (pid = read_xvfb_pid) && CliUtil.process_running?(pid)
197
211
  end
198
212
 
199
213
  def pid_filename
@@ -14,20 +14,21 @@ class Headless
14
14
  `which #{app}`.strip
15
15
  end
16
16
 
17
- def self.read_pid(pid_filename)
18
- pid = (File.read(pid_filename) rescue "").strip.to_i
19
- pid = nil if pid.zero?
17
+ def self.process_mine?(pid)
18
+ Process.kill(0, pid) && true
19
+ rescue Errno::EPERM, Errno::ESRCH
20
+ false
21
+ end
20
22
 
21
- if pid
22
- begin
23
- Process.kill(0, pid)
24
- pid
25
- rescue Errno::ESRCH
26
- nil
27
- end
28
- else
29
- nil
30
- end
23
+ def self.process_running?(pid)
24
+ Process.getpgid(pid) && true
25
+ rescue Errno::ESRCH
26
+ false
27
+ end
28
+
29
+ def self.read_pid(pid_filename)
30
+ pid = (File.read(pid_filename) rescue "").strip
31
+ pid.empty? ? nil : pid.to_i
31
32
  end
32
33
 
33
34
  def self.fork_process(command, pid_filename, log_filename='/dev/null')
@@ -43,7 +44,7 @@ class Headless
43
44
  end
44
45
 
45
46
  def self.kill_process(pid_filename, options={})
46
- if pid = self.read_pid(pid_filename)
47
+ if pid = read_pid(pid_filename)
47
48
  begin
48
49
  Process.kill 'TERM', pid
49
50
  Process.wait pid if options[:wait]
@@ -38,9 +38,12 @@ describe Headless do
38
38
  end
39
39
  end
40
40
 
41
- context "when Xvfb is already running" do
41
+ context "when Xvfb is already running and was started by this user" do
42
42
  before do
43
43
  allow(Headless::CliUtil).to receive(:read_pid).with('/tmp/.X99-lock').and_return(31337)
44
+ allow(Headless::CliUtil).to receive(:process_running?).with(31337).and_return(true)
45
+ allow(Headless::CliUtil).to receive(:process_mine?).with(31337).and_return(true)
46
+
44
47
  allow(Headless::CliUtil).to receive(:read_pid).with('/tmp/.X100-lock').and_return(nil)
45
48
  end
46
49
 
@@ -79,15 +82,18 @@ describe Headless do
79
82
 
80
83
  context 'when Xvfb is started, but by another user' do
81
84
  before do
82
- allow(Headless::CliUtil).to receive(:read_pid).with('/tmp/.X99-lock') { raise Errno::EPERM }
85
+ allow(Headless::CliUtil).to receive(:read_pid).with('/tmp/.X99-lock').and_return(31337)
86
+ allow(Headless::CliUtil).to receive(:process_running?).with(31337).and_return(true)
87
+ allow(Headless::CliUtil).to receive(:process_mine?).with(31337).and_return(false)
88
+
83
89
  allow(Headless::CliUtil).to receive(:read_pid).with('/tmp/.X100-lock').and_return(nil)
84
90
  end
85
91
 
86
92
  context "and display autopicking is not allowed" do
87
93
  let(:options) { {:autopick => false} }
88
94
 
89
- it "should fail with and exception" do
90
- expect { Headless.new(options) }.to raise_error(Headless::Exception)
95
+ it "should reuse the display" do
96
+ expect(Headless.new(options).display).to eq 99
91
97
  end
92
98
  end
93
99
 
@@ -176,6 +182,18 @@ describe Headless do
176
182
  expect { headless.take_screenshot('a.png', :using => :xwd) }.to raise_error(Headless::Exception)
177
183
  end
178
184
 
185
+ it "raises an error if gm is not installed with using: :graphicsmagick" do
186
+ allow(Headless::CliUtil).to receive(:application_exists?).with('gm').and_return(false)
187
+
188
+ expect { headless.take_screenshot('a.png', :using => :graphicsmagick) }.to raise_error(Headless::Exception)
189
+ end
190
+
191
+ it "raises an error if gm is not installed with using: :gm" do
192
+ allow(Headless::CliUtil).to receive(:application_exists?).with('gm').and_return(false)
193
+
194
+ expect { headless.take_screenshot('a.png', :using => :gm) }.to raise_error(Headless::Exception)
195
+ end
196
+
179
197
  it "issues command to take screenshot, with default options" do
180
198
  allow(Headless::CliUtil).to receive(:path_to).with('import').and_return('path/import')
181
199
  expect(headless).to receive(:system).with("path/import -display localhost:99 -window root /tmp/image.png")
@@ -193,6 +211,18 @@ describe Headless do
193
211
  expect(headless).to receive(:system).with("path/xwd -display localhost:99 -silent -root -out /tmp/image.png")
194
212
  headless.take_screenshot("/tmp/image.png", :using => :xwd)
195
213
  end
214
+
215
+ it "issues command to take screenshot, with using: :graphicsmagick" do
216
+ allow(Headless::CliUtil).to receive(:path_to).with('gm').and_return('path/gm')
217
+ expect(headless).to receive(:system).with("path/gm import -display localhost:99 -window root /tmp/image.png")
218
+ headless.take_screenshot("/tmp/image.png", :using => :graphicsmagick)
219
+ end
220
+
221
+ it "issues command to take screenshot, with using: :gm" do
222
+ allow(Headless::CliUtil).to receive(:path_to).with('gm').and_return('path/gm')
223
+ expect(headless).to receive(:system).with("path/gm import -display localhost:99 -window root /tmp/image.png")
224
+ headless.take_screenshot("/tmp/image.png", :using => :gm)
225
+ end
196
226
  end
197
227
  end
198
228
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: headless
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leonid Shevtsov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-10 00:00:00.000000000 Z
11
+ date: 2015-08-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -98,4 +98,3 @@ signing_key:
98
98
  specification_version: 4
99
99
  summary: Ruby headless display interface
100
100
  test_files: []
101
- has_rdoc: