kopflos 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,52 @@
1
+ = Kopflos
2
+
3
+ Yo dog, I heard you like testing in real browsers, so I put some virtual
4
+ framebuffer onto your system so you can test while you have no actual display.
5
+
6
+ == Installation
7
+
8
+ * Add 'kopflos' to your gemfile.
9
+ * Install xvfb.
10
+ * run your cucumber / steak / etc stories as before
11
+ * observe: no browser window popping up
12
+
13
+ Do not know how to support Mac yet. But which ruby developer uses a macintosh anyway?
14
+
15
+ === Cucumber
16
+
17
+ In your env.rb:
18
+
19
+ require 'kopflos/cucumber'
20
+
21
+ === RSpec
22
+
23
+ In your spec_helper.rb or rails_helper.rb
24
+
25
+ require 'kopflos/rspec'
26
+
27
+ == Future / TODO
28
+
29
+ * Screenshot support -- already build in, may get more sophisticated later (TM).
30
+ * disable kopflos temporarily -- to look at your browser from time to time
31
+
32
+ == Contributing to kopflos
33
+
34
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
35
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
36
+ * Fork the project
37
+ * Start a feature/bugfix branch
38
+ * Commit and push until you are happy with your contribution
39
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
40
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
41
+
42
+ == Gotchas
43
+
44
+ === Lockfiles
45
+
46
+ Xvfb may leave lockfiles behind (/tmp/.X999-lock). If they run out, Xvfb cannot be started and Selenium may fail with EOFError.
47
+
48
+ == Copyright
49
+
50
+ Copyright (c) 2011 Niklas Hofer. See LICENSE.txt for
51
+ further details.
52
+
@@ -0,0 +1,23 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'rspec/core'
13
+ require 'rspec/core/rake_task'
14
+ RSpec::Core::RakeTask.new(:spec) do |spec|
15
+ spec.pattern = FileList['spec/**/*_spec.rb']
16
+ end
17
+
18
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
19
+ spec.pattern = 'spec/**/*_spec.rb'
20
+ spec.rcov = true
21
+ end
22
+
23
+ task :default => :spec
@@ -0,0 +1,53 @@
1
+ # -*- encoding: utf-8 -*-
2
+ # stub: kopflos ruby lib
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "kopflos"
6
+ s.version = "0.0.2"
7
+
8
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
9
+ s.require_paths = ["lib"]
10
+ s.authors = ["Niklas Hofer"]
11
+ s.date = "2016-08-16"
12
+ s.description = "Starts a virtual framebuffer XServer (Xvfb) and redirects all X clients there"
13
+ s.email = "niklas+dev@lanpartei.de"
14
+ s.extra_rdoc_files = [
15
+ "LICENSE.txt",
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ "Gemfile",
20
+ "Gemfile.lock",
21
+ "LICENSE.txt",
22
+ "README.rdoc",
23
+ "Rakefile",
24
+ "kopflos.gemspec",
25
+ "lib/kopflos.rb",
26
+ "lib/kopflos/cucumber.rb",
27
+ "lib/kopflos/rspec.rb",
28
+ "lib/kopflos/xvfb.rb",
29
+ "spec/kopflos/xvfb_spec.rb",
30
+ "spec/kopflos_spec.rb",
31
+ "spec/spec_helper.rb"
32
+ ]
33
+ s.homepage = "http://github.com/niklas/kopflos"
34
+ s.licenses = ["MIT"]
35
+ s.rubygems_version = "2.5.1"
36
+ s.summary = "hides all your spawning X applications"
37
+
38
+ if s.respond_to? :specification_version then
39
+ s.specification_version = 4
40
+
41
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
42
+ s.add_development_dependency(%q<rspec>, [">= 2"])
43
+ s.add_development_dependency(%q<bundler>, [">= 0"])
44
+ else
45
+ s.add_dependency(%q<rspec>, [">= 2"])
46
+ s.add_dependency(%q<bundler>, [">= 0"])
47
+ end
48
+ else
49
+ s.add_dependency(%q<rspec>, [">= 2"])
50
+ s.add_dependency(%q<bundler>, [">= 0"])
51
+ end
52
+ end
53
+
@@ -0,0 +1,63 @@
1
+ require 'kopflos/xvfb'
2
+
3
+ module Kopflos
4
+ class AlreadyRunning < Exception; end
5
+
6
+ EnvVar = 'KOPFLOS'
7
+
8
+ def self.start(opts={})
9
+ if disabled_by_env_variable?
10
+ log "disabled through environment variable #{EnvVar} (set to anything else than 'false' or '0')"
11
+ return
12
+ end
13
+ if disabled_by_switch_file?
14
+ log "disabled through switch file #{switch_file_path} (unlink to hide the frakkin browser windows again)"
15
+ return
16
+ end
17
+ if running?
18
+ unless opts[:reuse]
19
+ raise AlreadyRunning, "there is already a server running: #{@server}"
20
+ end
21
+ else
22
+ @server = Xvfb.new(opts)
23
+ @server.start
24
+ @server
25
+ end
26
+ rescue Xvfb::NotSupported => e
27
+ log "your platform is not supported (yet): #{RUBY_PLATFORM}"
28
+ end
29
+
30
+ def self.stop
31
+ if running?
32
+ @server.stop
33
+ @server = nil
34
+ end
35
+ end
36
+
37
+ def self.running?
38
+ @server && @server.running?
39
+ end
40
+
41
+ def self.reset!
42
+ stop
43
+ @server = nil
44
+ end
45
+
46
+ def self.disabled_by_env_variable?
47
+ %w(false disabled 0 disable no).include?(ENV[EnvVar].to_s)
48
+ end
49
+
50
+ def self.disabled_by_switch_file?
51
+ File.exists?( switch_file_path )
52
+ end
53
+
54
+ def self.switch_file_path
55
+ 'kopflos_disabled'
56
+ end
57
+
58
+ def self.log(message)
59
+ if ENV['LOG']
60
+ STDERR.puts "#{self}: #{message}"
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,16 @@
1
+ require 'kopflos'
2
+
3
+ $kopflos_exit_code = 0
4
+
5
+ Before '@javascript' do |scenario|
6
+ Kopflos.start :reuse => true
7
+ end
8
+
9
+ After do |scenario|
10
+ $kopflos_exit_code = 1 if scenario.failed?
11
+ end
12
+
13
+ at_exit do
14
+ Kopflos.stop
15
+ exit($kopflos_exit_code)
16
+ end
@@ -0,0 +1,10 @@
1
+ require 'kopflos'
2
+
3
+ RSpec.configure do |config|
4
+ config.before(:each, :js) do
5
+ Kopflos.start :reuse => true
6
+ end
7
+ config.after(:suite) do
8
+ Kopflos.stop
9
+ end
10
+ end
@@ -0,0 +1,207 @@
1
+ require 'tempfile'
2
+ require 'open4'
3
+
4
+ # heavily inspired by selenium-client
5
+ # https://github.com/jodell/selenium-client/blob/da0f3dfbc05a6377c0cada09a3d650daf1261415/lib/xvfb/xvfb.rb
6
+ module Kopflos
7
+ class Xvfb
8
+ class Error < ::Exception; end
9
+
10
+ class NotInstalled < Error
11
+ def message
12
+ msg = <<-MSG
13
+ Could not find Xvfb in PATH.
14
+ Try installing xvfb via `sudo apt-get install xvfb`.
15
+ PATH: #{ENV['PATH']}
16
+ MSG
17
+ end
18
+ end
19
+
20
+ class NotSupported < Error; end
21
+
22
+ attr_accessor :font_path, :resolution, :screen, :wait
23
+
24
+ def initialize(options = {})
25
+ @font_path = options[:font_path] || self.class.determine_font_path
26
+ @resolution = options[:resolution] || '1024x768x24'
27
+ @screen = options[:screen] || '1'
28
+ @wait = options[:wait] || 5
29
+ @manager = options[:manager] || options[:wm]
30
+ raise NotSupported unless self.class.supported?
31
+ end
32
+
33
+ def self.start(options={})
34
+ xvfb = new(options)
35
+ xvfb.start
36
+ xvfb
37
+ end
38
+
39
+ def self.supported?
40
+ RUBY_PLATFORM =~ /linux/
41
+ end
42
+
43
+ def start
44
+ authorize
45
+ @server = start_server_in_subprocess
46
+ log "started => #{@server.pid}"
47
+ sleep @wait # FIXME is there a generic way to find out if Xvfb has started?
48
+ @window_manager = start_window_manager
49
+ log "finished"
50
+ end
51
+
52
+ def running?
53
+ !!@server && !!@server.status
54
+ end
55
+
56
+ def stop
57
+ kill_window_manager
58
+ kill_server
59
+ Process.wait
60
+ rescue Errno::ENOENT => e
61
+ rescue Errno::ECHILD => e
62
+ rescue Errno::EPIPE => e
63
+ ensure
64
+ if @servernum
65
+ File.unlink( lockfile ) if File.exist?(lockfile)
66
+ end
67
+ load_env @origin_env
68
+ @server = nil
69
+ end
70
+
71
+ def screenshot(filename='screenshot.png')
72
+ system 'import', '-window', 'root', filename
73
+ end
74
+
75
+ def command
76
+ [
77
+ executable,
78
+ ":#{servernum}",
79
+ '-br', # black background
80
+ "-fp", @font_path,
81
+ '-screen', @screen,
82
+ @resolution,
83
+ ]
84
+ end
85
+
86
+ def authorize
87
+ @origin_env = current_env
88
+ ENV['XAUTHORITY'] = authfile.path
89
+ ENV['DISPLAY'] = ":#{servernum}.#{@screen}"
90
+ IO.popen('xauth source -', 'w') do |xauth|
91
+ xauth.puts %Q~add :#{servernum} . #{mcookie}~
92
+ end
93
+ end
94
+
95
+ def to_s
96
+ "<#{self.class} :#{servernum}.#{@screen}>"
97
+ end
98
+
99
+
100
+ protected
101
+
102
+ def executable
103
+ return @executable if defined?(@executable)
104
+ @executable = `which Xvfb`.chomp
105
+ if @executable.empty?
106
+ raise NotInstalled
107
+ end
108
+ @executable
109
+ end
110
+
111
+ def self.determine_font_path
112
+ if RUBY_PLATFORM =~ /linux/
113
+ case `cat /etc/issue`
114
+ when /Debian|Ubuntu/
115
+ return '/usr/share/fonts/X11/misc'
116
+ else
117
+ return ENV['XVFB_FONT_PATH'] if ENV['XVFB_FONT_PATH']
118
+ end
119
+ end
120
+ raise NotSupported, "#{RUBY_PLATFORM} not supported by default, Export $XVFB_FONT_PATH with a path to your X11 fonts/misc directory"
121
+ end
122
+
123
+ def authfile
124
+ @authfile ||= Tempfile.new('kopflos_Xauthority')
125
+ end
126
+
127
+ def mcookie
128
+ @mcookie ||= `mcookie`.chomp
129
+ end
130
+
131
+
132
+ # Find a free server number by looking at .X*-lock files in /tmp.
133
+ def find_free_servernum
134
+ servernum = 99
135
+ while File.exists?( lockfile( servernum ) )
136
+ servernum += 1
137
+ end
138
+ servernum
139
+ end
140
+
141
+ def servernum
142
+ @servernum ||= find_free_servernum
143
+ end
144
+
145
+ def start_window_manager
146
+ return unless @manager
147
+ Open4::background @manager
148
+ rescue Errno::ECHILD => e
149
+ log "could not start window manager: #{e}"
150
+ end
151
+
152
+ def log(message)
153
+ message = "#{self} #{message}"
154
+ if defined?(Rails)
155
+ Rails.logger.info(message)
156
+ else
157
+ STDERR.puts message
158
+ end
159
+ end
160
+
161
+ def lockfile(num=servernum)
162
+ "/tmp/.X#{num}-lock"
163
+ end
164
+
165
+ def start_server_in_subprocess
166
+ empty = ''
167
+ log "starting: #{command.join(' ')}"
168
+ Open4::background(command, 0 => empty, 1 => empty, 2 => empty, 'ignore_exit_failure' => true)
169
+ rescue Errno::ECHILD => e
170
+ log "could not start server: #{e}"
171
+ end
172
+
173
+ def kill_server
174
+ if @server
175
+ # first, kill the X server
176
+ Process.kill("USR1", @server.pid)
177
+ # then wait for the thread to finish
178
+ @server.join
179
+ else
180
+ log "no server found to kill"
181
+ end
182
+ end
183
+
184
+ def kill_window_manager
185
+ if @window_manager && @window_manager.respond_to?(:pid)
186
+ # first, kill the external process
187
+ Process.kill("USR1", @window_manager.pid)
188
+ # then wait for the thread to finish
189
+ @window_manager.join
190
+ else
191
+ log "no server found to kill"
192
+ end
193
+ end
194
+
195
+ def current_env
196
+ { :display => ENV['DISPLAY'], :xauthority => ENV['XAUTHORITY'] }
197
+ end
198
+
199
+ def load_env(env)
200
+ if env && env.respond_to?(:[])
201
+ ENV['DISPLAY'], ENV['XAUTHORITY'] = env[:display], env[:xauthority]
202
+ end
203
+ end
204
+
205
+ end
206
+
207
+ end
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+
3
+ module Kopflos
4
+ describe Xvfb do
5
+ it "should be a class" do
6
+ Xvfb.should be_a(Class)
7
+ end
8
+
9
+ it "should determine font path on initialization" do
10
+ Xvfb.should_receive(:determine_font_path)
11
+ Xvfb.new :wait => 1
12
+ end
13
+
14
+
15
+ describe 'new' do
16
+ before :each do
17
+ process = mock("process running in background", :pid => 0, :status => 'sleep')
18
+ Xvfb.stub!(:determine_font_path).and_return('/usr/share/fonts/X11/misc')
19
+ @xvfb = Xvfb.new
20
+ Open4.stub!(:background).and_return( process )
21
+ @xvfb.stub!(:kill_server).and_return(true)
22
+ end
23
+
24
+ after :each do
25
+ @xvfb.stop
26
+ end
27
+
28
+ it "should set font path" do
29
+ @xvfb.font_path.should == '/usr/share/fonts/X11/misc'
30
+ end
31
+
32
+ it "should have default resolution" do
33
+ @xvfb.resolution.should == '1024x768x24'
34
+ end
35
+
36
+ it "should have default screen" do
37
+ @xvfb.screen.should == '1'
38
+ end
39
+
40
+ it "should have a default wait time of 5s" do
41
+ @xvfb.wait.should == 5
42
+ end
43
+
44
+ it "should authorize itself on startup" do
45
+ @xvfb.should_receive(:authorize).and_return(true)
46
+ @xvfb.start
47
+ end
48
+
49
+ it "should reflect its status" do
50
+ @xvfb.start
51
+ @xvfb.should be_running
52
+ @xvfb.stop
53
+ @xvfb.should_not be_running
54
+ end
55
+
56
+ it "should set DISPLAY and XAUTHORITY env vars" do
57
+ display, xauthority = ENV['DISPLAY'], ENV['XAUTHORITY']
58
+ @xvfb.authorize
59
+
60
+ ENV['DISPLAY'].should_not be_nil
61
+ ENV['DISPLAY'].should_not be_empty
62
+ ENV['DISPLAY'].should_not == display
63
+ ENV['XAUTHORITY'].should_not be_nil
64
+ ENV['XAUTHORITY'].should_not be_empty
65
+ ENV['XAUTHORITY'].should_not == xauthority
66
+ end
67
+
68
+ describe "in forked process" do
69
+ before :each do
70
+ @xvfb.stub!(:authorize).and_return(true)
71
+ @xvfb.stub!(:execute).and_return(true) # not really, should NOT return, it calles Kernel#exec
72
+ end
73
+ it "should start window manager" do
74
+ @xvfb.should_receive(:start_window_manager).and_return(true)
75
+ @xvfb.start
76
+ end
77
+ end
78
+ end
79
+
80
+ end
81
+ end