headless 0.3.1 → 1.0.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.
- data/CHANGELOG +5 -0
- data/README.md +23 -0
- data/headless.gemspec +2 -1
- data/lib/headless.rb +19 -27
- data/lib/headless/cli_util.rb +1 -2
- data/lib/headless/video/video_recorder.rb +2 -1
- data/spec/headless_spec.rb +2 -2
- metadata +32 -5
data/CHANGELOG
CHANGED
data/README.md
CHANGED
@@ -10,6 +10,8 @@ Documentation is available at [rdoc.info](http://rdoc.info/projects/leonid-shevt
|
|
10
10
|
|
11
11
|
[Changelog](https://github.com/leonid-shevtsov/headless/blob/master/CHANGELOG)
|
12
12
|
|
13
|
+
**Note: Headless will NOT hide most applications on OS X. [Here is a detailed explanation](https://github.com/leonid-shevtsov/headless/issues/31#issuecomment-8933108)**
|
14
|
+
|
13
15
|
## Installation
|
14
16
|
|
15
17
|
On Debian/Ubuntu:
|
@@ -59,6 +61,27 @@ Running cucumber headless is now as simple as adding a before and after hook in
|
|
59
61
|
headless.start
|
60
62
|
end
|
61
63
|
|
64
|
+
## Running tests in parallel
|
65
|
+
|
66
|
+
If you have multiple threads running acceptance tests in parallel, you want to spawn Headless before forking, and then reuse that instance with `destroy_at_exit: false`.
|
67
|
+
You can even spawn a Headless instance in one ruby script, and then reuse the same instance in other scripts by specifying the same display number and `reuse: true`.
|
68
|
+
|
69
|
+
# spawn_headless.rb
|
70
|
+
Headless.new(display: 100, destroy_at_exit: false).start
|
71
|
+
|
72
|
+
|
73
|
+
# test_suite_that_could_be_ran_multiple_times.rb
|
74
|
+
headless = Headless.new(display: 100, reuse: true, destroy_at_exit: false)
|
75
|
+
# Xvfb is already started by the first script
|
76
|
+
|
77
|
+
# reap_headless.rb
|
78
|
+
headless = Headless.new(display: 100, reuse: true)
|
79
|
+
headless.destroy
|
80
|
+
|
81
|
+
|
82
|
+
|
83
|
+
|
84
|
+
|
62
85
|
## Cucumber with wkhtmltopdf
|
63
86
|
|
64
87
|
_Note: this is true for other programs which may use headless at the same time as cucumber is running_
|
data/headless.gemspec
CHANGED
@@ -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 = '0.
|
6
|
+
s.version = '1.0.0'
|
7
7
|
s.summary = 'Ruby headless display interface'
|
8
8
|
|
9
9
|
s.description = <<-EOF
|
@@ -14,5 +14,6 @@ spec = Gem::Specification.new do |s|
|
|
14
14
|
|
15
15
|
s.files = `git ls-files`.split("\n")
|
16
16
|
|
17
|
+
s.add_development_dependency 'rake'
|
17
18
|
s.add_development_dependency "rspec", "~> 2.6"
|
18
19
|
end
|
data/lib/headless.rb
CHANGED
@@ -15,11 +15,11 @@ require 'headless/video/video_recorder'
|
|
15
15
|
# require 'rubygems'
|
16
16
|
# require 'headless'
|
17
17
|
# require 'selenium-webdriver'
|
18
|
-
#
|
18
|
+
#
|
19
19
|
# Headless.ly do
|
20
20
|
# driver = Selenium::WebDriver.for :firefox
|
21
21
|
# driver.navigate.to 'http://google.com'
|
22
|
-
# puts driver.title
|
22
|
+
# puts driver.title
|
23
23
|
# end
|
24
24
|
#
|
25
25
|
# Object mode:
|
@@ -33,7 +33,7 @@ require 'headless/video/video_recorder'
|
|
33
33
|
#
|
34
34
|
# driver = Selenium::WebDriver.for :firefox
|
35
35
|
# driver.navigate.to 'http://google.com'
|
36
|
-
# puts driver.title
|
36
|
+
# puts driver.title
|
37
37
|
#
|
38
38
|
# headless.destroy
|
39
39
|
#--
|
@@ -42,6 +42,7 @@ require 'headless/video/video_recorder'
|
|
42
42
|
class Headless
|
43
43
|
|
44
44
|
DEFAULT_DISPLAY_NUMBER = 99
|
45
|
+
MAX_DISPLAY_NUMBER = 10_000
|
45
46
|
DEFAULT_DISPLAY_DIMENSIONS = '1280x1024x24'
|
46
47
|
|
47
48
|
class Exception < RuntimeError
|
@@ -102,6 +103,7 @@ class Headless
|
|
102
103
|
headless = Headless.new(options)
|
103
104
|
headless.start
|
104
105
|
yield headless
|
106
|
+
ensure
|
105
107
|
headless.destroy
|
106
108
|
end
|
107
109
|
class <<self; alias_method :ly, :run; end
|
@@ -119,38 +121,28 @@ class Headless
|
|
119
121
|
private
|
120
122
|
|
121
123
|
def attach_xvfb
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
begin
|
126
|
-
if !xvfb_running?
|
127
|
-
launch_xvfb
|
128
|
-
success=true
|
129
|
-
else
|
130
|
-
success = @reuse_display
|
131
|
-
end
|
132
|
-
rescue Errno::EPERM
|
133
|
-
# No permission to read pid file
|
134
|
-
success = false
|
135
|
-
end
|
124
|
+
possible_display_set = @autopick_display ? @display..MAX_DISPLAY_NUMBER : Array(@display)
|
125
|
+
pick_available_display(possible_display_set, @reuse_display)
|
126
|
+
end
|
136
127
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
128
|
+
def pick_available_display(display_set, can_reuse)
|
129
|
+
display_set.each do |display_number|
|
130
|
+
@display = display_number
|
131
|
+
begin
|
132
|
+
return true if xvfb_running? && can_reuse
|
133
|
+
return true if !xvfb_running? && launch_xvfb
|
134
|
+
rescue Errno::EPERM # display not accessible
|
135
|
+
next
|
142
136
|
end
|
143
137
|
end
|
144
|
-
|
145
|
-
unless success
|
146
|
-
raise Headless::Exception.new("Display :#{display} is already taken and reuse=false")
|
147
|
-
end
|
138
|
+
raise Headless::Exception.new("Could not find an available display")
|
148
139
|
end
|
149
140
|
|
150
141
|
def launch_xvfb
|
151
142
|
#TODO error reporting
|
152
143
|
result = system "#{CliUtil.path_to("Xvfb")} :#{display} -screen 0 #{dimensions} -ac >/dev/null 2>&1 &"
|
153
144
|
raise Headless::Exception.new("Xvfb did not launch - something's wrong") unless result
|
145
|
+
return true
|
154
146
|
end
|
155
147
|
|
156
148
|
def xvfb_running?
|
@@ -164,7 +156,7 @@ private
|
|
164
156
|
def read_xvfb_pid
|
165
157
|
CliUtil.read_pid(pid_filename)
|
166
158
|
end
|
167
|
-
|
159
|
+
|
168
160
|
def hook_at_exit
|
169
161
|
unless @at_exit_hook_installed
|
170
162
|
@at_exit_hook_installed = true
|
data/lib/headless/cli_util.rb
CHANGED
@@ -3,7 +3,7 @@ class Headless
|
|
3
3
|
def self.application_exists?(app)
|
4
4
|
`which #{app}`.strip != ""
|
5
5
|
end
|
6
|
-
|
6
|
+
|
7
7
|
def self.ensure_application_exists!(app, error_message)
|
8
8
|
if !self.application_exists?(app)
|
9
9
|
raise Headless::Exception.new(error_message)
|
@@ -36,7 +36,6 @@ class Headless
|
|
36
36
|
exec command
|
37
37
|
exit! 127 # safeguard in case exec fails
|
38
38
|
end
|
39
|
-
Process.detach(pid)
|
40
39
|
|
41
40
|
File.open pid_filename, 'w' do |f|
|
42
41
|
f.puts pid
|
@@ -14,6 +14,7 @@ class Headless
|
|
14
14
|
@tmp_file_path = options.fetch(:tmp_file_path, "/tmp/.headless_ffmpeg_#{@display}.mov")
|
15
15
|
@log_file_path = options.fetch(:log_file_path, "/dev/null")
|
16
16
|
@codec = options.fetch(:codec, "qtrle")
|
17
|
+
@frame_rate = options.fetch(:frame_rate, 30)
|
17
18
|
end
|
18
19
|
|
19
20
|
def capture_running?
|
@@ -21,7 +22,7 @@ class Headless
|
|
21
22
|
end
|
22
23
|
|
23
24
|
def start_capture
|
24
|
-
CliUtil.fork_process("#{CliUtil.path_to('ffmpeg')} -y -r
|
25
|
+
CliUtil.fork_process("#{CliUtil.path_to('ffmpeg')} -y -r #{@frame_rate} -g 600 -s #{@dimensions} -f x11grab -i :#{@display} -vcodec #{@codec} #{@tmp_file_path}", @pid_file_path, @log_file_path)
|
25
26
|
at_exit do
|
26
27
|
exit_status = $!.status if $!.is_a?(SystemExit)
|
27
28
|
stop_and_discard
|
data/spec/headless_spec.rb
CHANGED
@@ -44,10 +44,10 @@ describe Headless do
|
|
44
44
|
Headless.new(options).display.should == 99
|
45
45
|
end
|
46
46
|
end
|
47
|
-
|
47
|
+
|
48
48
|
context "and display reuse is not allowed" do
|
49
49
|
let(:options) { {:reuse => false} }
|
50
|
-
|
50
|
+
|
51
51
|
it "should pick the next available display number" do
|
52
52
|
Headless.new(options).display.should == 100
|
53
53
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: headless
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,27 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2013-01-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
14
30
|
- !ruby/object:Gem::Dependency
|
15
31
|
name: rspec
|
16
|
-
requirement:
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
17
33
|
none: false
|
18
34
|
requirements:
|
19
35
|
- - ~>
|
@@ -21,7 +37,12 @@ dependencies:
|
|
21
37
|
version: '2.6'
|
22
38
|
type: :development
|
23
39
|
prerelease: false
|
24
|
-
version_requirements:
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '2.6'
|
25
46
|
description: ! ' Headless is a Ruby interface for Xvfb. It allows you to create
|
26
47
|
a headless display straight from Ruby code, hiding some low-level action.
|
27
48
|
|
@@ -56,16 +77,22 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
56
77
|
- - ! '>='
|
57
78
|
- !ruby/object:Gem::Version
|
58
79
|
version: '0'
|
80
|
+
segments:
|
81
|
+
- 0
|
82
|
+
hash: 2986386947499728446
|
59
83
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
84
|
none: false
|
61
85
|
requirements:
|
62
86
|
- - ! '>='
|
63
87
|
- !ruby/object:Gem::Version
|
64
88
|
version: '0'
|
89
|
+
segments:
|
90
|
+
- 0
|
91
|
+
hash: 2986386947499728446
|
65
92
|
requirements:
|
66
93
|
- Xvfb
|
67
94
|
rubyforge_project:
|
68
|
-
rubygems_version: 1.8.
|
95
|
+
rubygems_version: 1.8.24
|
69
96
|
signing_key:
|
70
97
|
specification_version: 3
|
71
98
|
summary: Ruby headless display interface
|