diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..17558cd --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source "http://rubygems.org" + +# Specify your gem's dependencies in ci_util.gemspec +gemspec diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..2995527 --- /dev/null +++ b/Rakefile @@ -0,0 +1 @@ +require "bundler/gem_tasks" diff --git a/.gemspec b/headless.gemspec similarity index 85% rename from .gemspec rename to headless.gemspec index 821496b..af86656 100644 --- a/.gemspec +++ b/headless.gemspec @@ -3,7 +3,7 @@ require 'rake' spec = Gem::Specification.new do |s| s.author = 'Leonid Shevtsov' s.email = 'leonid@shevtsov.me' - + s.name = 'headless' s.version = '0.1.0' s.summary = 'Ruby headless display interface' @@ -16,4 +16,7 @@ spec = Gem::Specification.new do |s| s.files = FileList['lib/*.rb', '[A-Z]*'].to_a s.has_rdoc = true + + s.add_development_dependency "rspec", "~> 2.6" + s.add_development_dependency "ruby-debug" end diff --git a/lib/headless.rb b/lib/headless.rb index f81200c..b60d7ed 100644 --- a/lib/headless.rb +++ b/lib/headless.rb @@ -1,3 +1,5 @@ +require 'headless/cli_util' + # A class incapsulating the creation and usage of a headless X server # # == Prerequisites @@ -54,7 +56,7 @@ class Exception < ::Exception # * +reuse+ (default true) - if given display server already exists, should we use it or fail miserably? # * +dimensions+ (default 1280x1024x24) - display dimensions and depth. Not all combinations are possible, refer to +man Xvfb+. def initialize(options = {}) - find_xvfb + raise Exception.new("Xvfb not found on your system") unless CliUtil.application_exists?("Xvfb") @display = options.fetch(:display, 99).to_i @reuse_display = options.fetch(:reuse, true) @@ -62,14 +64,12 @@ def initialize(options = {}) #TODO more logic here, autopicking the display number if @reuse_display - launch_xvfb unless read_pid - elsif read_pid + launch_xvfb unless xvfb_running? + elsif xvfb_running? raise Exception.new("Display :#{display} is already taken and reuse=false") else launch_xvfb end - - raise Exception.new("Xvfb did not launch - something's wrong") unless read_pid end # Switches to the headless server @@ -86,7 +86,7 @@ def stop # Switches back from the headless server and terminates the headless session def destroy stop - Process.kill('TERM', xvfb_pid) if read_pid + Process.kill('TERM', read_xvfb_pid) if xvfb_running? end # Block syntax: @@ -101,27 +101,22 @@ def self.run(options={}, &block) yield headless headless.destroy end - class </dev/null 2>&1 &" - sleep 1 + result = system "#{CliUtil.path_to("Xvfb")} :#{display} -screen 0 #{dimensions} -ac >/dev/null 2>&1 &" + raise Exception.new("Xvfb did not launch - something's wrong") unless result + end + + def xvfb_running? + read_xvfb_pid end - def read_pid - @xvfb_pid=(File.read("/tmp/.X#{display}-lock") rescue "").strip.to_i - @xvfb_pid=nil if @xvfb_pid==0 - @xvfb_pid + def read_xvfb_pid #TODO maybe check that the process still exists + CliUtil.read_pid("/tmp/.X#{display}-lock") end end diff --git a/lib/headless/cli_util.rb b/lib/headless/cli_util.rb new file mode 100644 index 0000000..8a95f07 --- /dev/null +++ b/lib/headless/cli_util.rb @@ -0,0 +1,36 @@ +class CliUtil + def self.application_exists?(app) + `which #{app}`.strip == "" + end + + def self.path_to(app) + `which #{app}`.strip + end + + def self.read_pid(file) + pid = (File.read("/tmp/.X#{display}-lock") rescue "").strip.to_i + pid == 0 ? nil : pid + end + + def self.fork_process(command, pid_file) + pid = fork do + exec command + exit! 127 + end + + File.open pid_file, 'w' do |f| + f.puts pid + end + end + + def self.kill_process(pid_file) + if File.exist? pid_file + pid = File.read(pid_file).strip.to_i + Process.kill 'TERM', pid + FileUtils.rm pid_file + else + puts "#{pid_file} not found" + end + end + +end \ No newline at end of file diff --git a/spec/headless_spec.rb b/spec/headless_spec.rb new file mode 100644 index 0000000..8c064bf --- /dev/null +++ b/spec/headless_spec.rb @@ -0,0 +1,110 @@ +require 'lib/headless' + +describe Headless do + before do + ENV['DISPLAY'] = ":31337" + end + + context "instaniation" do + context "when Xvfb is not installed" do + before do + CliUtil.stub!(:application_exists?).and_return(false) + end + + it "raises an error" do + lambda { Headless.new }.should raise_error(Headless::Exception) + end + end + + context "when Xvfb not started yet" do + before do + stub_environment + end + + it "starts Xvfb" do + Headless.any_instance.should_receive(:system).with("/usr/bin/Xvfb :99 -screen 0 1280x1024x24 -ac >/dev/null 2>&1 &").and_return(true) + + headless = Headless.new + end + + it "allows setting screen dimensions" do + Headless.any_instance.should_receive(:system).with("/usr/bin/Xvfb :99 -screen 0 1024x768x16 -ac >/dev/null 2>&1 &").and_return(true) + + headless = Headless.new(:dimensions => "1024x768x16") + end + end + + context "when Xvfb is already running" do + before do + stub_environment + CliUtil.stub!(:read_pid).and_return(31337) + end + + it "raises an error if reuse display is not allowed" do + lambda { Headless.new(:reuse => false) }.should raise_error(Headless::Exception) + end + + it "doesn't raise an error if reuse display is allowed" do + lambda { Headless.new(:reuse => true) }.should_not raise_error(Headless::Exception) + lambda { Headless.new }.should_not raise_error(Headless::Exception) + end + end + end + + describe "#start" do + before do + stub_environment + + @headless = Headless.new + end + + it "switches to the headless server" do + ENV['DISPLAY'].should == ":31337" + @headless.start + ENV['DISPLAY'].should == ":99" + end + end + + describe "#stop" do + before do + stub_environment + + @headless = Headless.new + end + + it "switches back from the headless server" do + ENV['DISPLAY'].should == ":31337" + @headless.start + ENV['DISPLAY'].should == ":99" + @headless.stop + ENV['DISPLAY'].should == ":31337" + end + end + + describe "#destroy" do + before do + stub_environment + @headless = Headless.new + + CliUtil.stub!(:read_pid).and_return(4444) + end + + it "switches back from the headless server and terminates the headless session" do + Process.should_receive(:kill).with('TERM', 4444) + + ENV['DISPLAY'].should == ":31337" + @headless.start + ENV['DISPLAY'].should == ":99" + @headless.destroy + ENV['DISPLAY'].should == ":31337" + end + end + +private + + def stub_environment + CliUtil.stub!(:application_exists?).and_return(true) + CliUtil.stub!(:read_pid).and_return(nil) + CliUtil.stub!(:path_to).and_return("/usr/bin/Xvfb") + end +end \ No newline at end of file