cukeforker 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,87 @@
1
+ module CukeForker
2
+ class Worker
3
+ class << self
4
+ attr_writer :id
5
+ def id; @id ||= -1; end
6
+ end
7
+
8
+ attr_reader :status, :feature, :pid, :format, :out, :id
9
+ attr_accessor :vnc
10
+
11
+ def initialize(feature, format, out, extra_args = [])
12
+ @feature = feature
13
+ @format = format
14
+ @extra_args = extra_args
15
+ @out = out
16
+ @status, @vnc = nil
17
+
18
+ @id = self.class.id += 1
19
+ end
20
+
21
+ def finished?
22
+ wait_pid, @status = Process.waitpid2(pid, Process::WNOHANG)
23
+ !!wait_pid
24
+ rescue Errno::ECHILD, Errno::ESRCH
25
+ true
26
+ end
27
+
28
+ def failed?
29
+ status.nil? || status.exitstatus != 0
30
+ end
31
+
32
+ def start
33
+ @pid = Process.fork { execute_cucumber }
34
+ end
35
+
36
+ def args
37
+ args = %W[--format #{format} --out #{output}]
38
+ args += @extra_args
39
+ args << feature
40
+
41
+ args
42
+ end
43
+
44
+ def text
45
+ "[
46
+ #{pid}
47
+ #{feature}
48
+ #{status.inspect}
49
+ #{out}
50
+ #{vnc && vnc.display}
51
+ ]"
52
+ end
53
+
54
+ def output
55
+ File.join out, "#{basename}.#{format}"
56
+ end
57
+
58
+ def stdout
59
+ File.join out, "#{basename}.stdout"
60
+ end
61
+
62
+ def stderr
63
+ File.join out, "#{basename}.stderr"
64
+ end
65
+
66
+ private
67
+
68
+ def execute_cucumber
69
+ FileUtils.mkdir_p(out) unless File.exist? out
70
+
71
+ $stdout.reopen stdout
72
+ $stderr.reopen stderr
73
+
74
+ if @vnc
75
+ ENV['DISPLAY'] = @vnc.display
76
+ end
77
+
78
+ failed = Cucumber::Cli::Main.execute args
79
+ exit failed ? 1 : 0
80
+ end
81
+
82
+ def basename
83
+ @basename ||= feature.gsub(/\W/, '_')
84
+ end
85
+
86
+ end # Worker
87
+ end # CukeForker
@@ -0,0 +1,113 @@
1
+ module CukeForker
2
+ class WorkerQueue
3
+ include Observable
4
+
5
+ def initialize(max)
6
+ @max = max
7
+
8
+ @pending = []
9
+ @running = []
10
+ @finished = []
11
+ end
12
+
13
+ def backed_up?
14
+ @pending.any?
15
+ end
16
+
17
+ def add(worker)
18
+ @pending << worker
19
+ end
20
+
21
+ def process(poll_interval = nil)
22
+ @start_time = Time.now
23
+
24
+ while backed_up?
25
+ fill
26
+ eta
27
+ poll poll_interval while full?
28
+ end
29
+
30
+ # yay, no more pending workers
31
+ end
32
+
33
+ def wait_until_finished(poll_interval = nil)
34
+ until empty?
35
+ poll poll_interval
36
+ eta
37
+ end
38
+ end
39
+
40
+ def fill
41
+ while backed_up? and not full?
42
+ worker = @pending.shift
43
+ start worker
44
+ end
45
+ end
46
+
47
+ def poll(seconds = nil)
48
+ finished = @running.select { |w| w.finished? }
49
+
50
+ if finished.empty?
51
+ sleep seconds if seconds
52
+ else
53
+ finished.each { |w| finish w }
54
+ end
55
+ end
56
+
57
+ def size
58
+ @running.size
59
+ end
60
+
61
+ def full?
62
+ size == @max
63
+ end
64
+
65
+ def empty?
66
+ @running.empty?
67
+ end
68
+
69
+ def has_failures?
70
+ @finished.any? { |w| w.failed? }
71
+ end
72
+
73
+ private
74
+
75
+ def start(worker)
76
+ fire :on_worker_starting, worker
77
+
78
+ worker.start
79
+ @running << worker
80
+ end
81
+
82
+ def finish(worker)
83
+ @running.delete worker
84
+ @finished << worker
85
+
86
+ fire :on_worker_finished, worker
87
+ end
88
+
89
+ def eta
90
+ return Time.now if @finished.empty?
91
+
92
+ pending = @pending.size
93
+ finished = @finished.size
94
+
95
+ seconds_per_child = (Time.now - start_time) / finished
96
+ eta = Time.now + (seconds_per_child * pending)
97
+
98
+ fire :on_eta, eta, pending + size, finished
99
+ end
100
+
101
+ def fire(*args)
102
+ changed
103
+ notify_observers(*args)
104
+ end
105
+
106
+ def start_time
107
+ @start_time or raise NotStartedError
108
+ end
109
+
110
+ class NotStartedError < StandardError; end
111
+
112
+ end # WorkerQueue
113
+ end # CukeForker
@@ -0,0 +1,45 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ module CukeForker
4
+ describe LoggingListener do
5
+ let(:stdout) { StringIO.new }
6
+ let(:listener) { LoggingListener.new stdout }
7
+
8
+ it "logs all events" do
9
+ Time.stub(:now => Time.now)
10
+
11
+ mock_worker = mock(Worker, :id => "1", :feature => "foo/bar")
12
+ mock_display = mock(VncServer)
13
+ mock_display.stub(:display).and_return(nil, ":5")
14
+
15
+ listener.on_run_starting
16
+ listener.on_display_starting mock_display
17
+ listener.on_display_fetched mock_display
18
+ listener.on_worker_starting mock_worker
19
+ listener.on_eta Time.now, 10, 255
20
+ listener.on_worker_finished mock_worker
21
+ listener.on_display_released mock_display
22
+ listener.on_run_interrupted
23
+ listener.on_run_finished false
24
+ listener.on_display_stopping mock_display
25
+
26
+ timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S##{Process.pid}")
27
+
28
+ stdout.string.should == <<-OUTPUT
29
+ I, [#{timestamp}] INFO -- : [ run ] starting
30
+ I, [#{timestamp}] INFO -- : [ display( ) ] starting
31
+ I, [#{timestamp}] INFO -- : [ display(:5) ] fetched
32
+ I, [#{timestamp}] INFO -- : [ worker(1) ] starting: foo/bar
33
+ I, [#{timestamp}] INFO -- : [ eta(10/255) ] #{Time.now.strftime "%Y-%m-%d %H:%M:%S"}
34
+ I, [#{timestamp}] INFO -- : [ worker(1) ] finished: foo/bar
35
+ I, [#{timestamp}] INFO -- : [ display(:5) ] released
36
+ I, [#{timestamp}] INFO -- : [ run ] interrupted - please wait
37
+ I, [#{timestamp}] INFO -- : [ run ] finished, passed
38
+ I, [#{timestamp}] INFO -- : [ display(:5) ] stopping
39
+ OUTPUT
40
+ end
41
+
42
+
43
+
44
+ end # Worker
45
+ end # CukeForker
@@ -0,0 +1,84 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ module CukeForker
4
+ describe Runner do
5
+
6
+ context "creating" do
7
+ it "sets up a new instance" do
8
+ # sigh.
9
+
10
+ max = 4
11
+ format = :json
12
+ out = "/tmp"
13
+ listeners = [mock(AbstractListener, :update => nil)]
14
+ log = false
15
+ features = %w[a b]
16
+
17
+ mock_queue = mock(WorkerQueue)
18
+ mock_workers = Array.new(2) { |n| mock("Worker-#{n}") }
19
+
20
+ Process.stub(:pid => 1234)
21
+
22
+ WorkerQueue.should_receive(:new).with(max).and_return mock_queue
23
+ Worker.should_receive(:new).with("a", :json, "/tmp/1234", []).and_return mock_workers[0]
24
+ Worker.should_receive(:new).with("b", :json, "/tmp/1234", []).and_return mock_workers[1]
25
+
26
+ mock_queue.should_receive(:add_observer).once.with listeners.first
27
+ mock_queue.should_receive(:add).with mock_workers[0]
28
+ mock_queue.should_receive(:add).with mock_workers[1]
29
+
30
+ Runner.create(features,
31
+ :max => max,
32
+ :notify => listeners,
33
+ :format => format,
34
+ :log => false,
35
+ :out => out
36
+ ).should be_kind_of(Runner)
37
+ end
38
+
39
+ it "sets up the VNC pool if :vnc => true" do
40
+ mock_pool = mock(VncServerPool, :add_observer => nil)
41
+ VncServerPool.should_receive(:new).with(2).and_return mock_pool
42
+ VncListener.should_receive(:new).with(mock_pool).and_return mock(:update => nil)
43
+
44
+ Runner.create([], :max => 2, :vnc => true)
45
+ end
46
+
47
+ it "creates and runs a new runner" do
48
+ r = mock(Runner)
49
+ Runner.should_receive(:create).with(%w[a b], {}).and_return(r)
50
+ r.should_receive(:run)
51
+
52
+ Runner.run(%w[a b])
53
+ end
54
+ end
55
+
56
+ context "running" do
57
+ let(:listener) { mock(AbstractListener, :update => nil) }
58
+ let(:queue) { mock(Queue, :has_failures? => false) }
59
+ let(:runner) { Runner.new(queue) }
60
+
61
+ it "processes the queue" do
62
+ runner.add_observer listener
63
+
64
+ listener.should_receive(:update).with(:on_run_starting)
65
+ queue.should_receive(:process).with 0.2 # poll interval
66
+ queue.should_receive(:wait_until_finished)
67
+ listener.should_receive(:update).with(:on_run_finished, false)
68
+
69
+ runner.run
70
+ end
71
+
72
+ it "fires on_run_interrupted and shuts down if the run is interrupted" do
73
+ runner.add_observer listener
74
+
75
+ queue.stub(:process).and_raise(Interrupt)
76
+ runner.stub(:stop)
77
+ listener.should_receive(:update).with(:on_run_interrupted)
78
+
79
+ runner.run
80
+ end
81
+ end
82
+
83
+ end # Runner
84
+ end # CukeForker
@@ -0,0 +1,32 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ module CukeForker
4
+ describe VncListener do
5
+ let(:server) { mock(VncServer) }
6
+ let(:pool) { mock(VncServerPool) }
7
+ let(:worker) { mock(Worker) }
8
+ let(:listener) { VncListener.new pool }
9
+
10
+ it "fetches a display from the pool and assings it to the worker" do
11
+ pool.should_receive(:get).and_return(server)
12
+ worker.should_receive(:vnc=).with server
13
+
14
+ listener.on_worker_starting worker
15
+ end
16
+
17
+ it "releases the display and removes it from the worker" do
18
+ worker.should_receive(:vnc).and_return server
19
+ pool.should_receive(:release).with server
20
+ worker.should_receive(:vnc=).with(nil)
21
+
22
+ listener.on_worker_finished worker
23
+ end
24
+
25
+ it "stops the pool when the run finishes" do
26
+ pool.should_receive(:stop)
27
+
28
+ listener.on_run_finished(true)
29
+ end
30
+
31
+ end # VncListenerServer
32
+ end # CukeForker
@@ -0,0 +1,62 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ module CukeForker
4
+ describe VncServerPool do
5
+ let(:pool) { VncServerPool.new(3, SpecHelper::FakeVnc) }
6
+
7
+ it "creates 3 instances of the given display class" do
8
+ SpecHelper::FakeVnc.should_receive(:new).exactly(3).times
9
+
10
+ pool = VncServerPool.new(3, SpecHelper::FakeVnc)
11
+ pool.size.should == 3
12
+ end
13
+
14
+ it "can fetch a server from the pool" do
15
+ pool.get.should be_kind_of(SpecHelper::FakeVnc)
16
+ pool.size.should == 2
17
+ end
18
+
19
+ it "can release a server" do
20
+ obj = pool.get
21
+ pool.size.should == 2
22
+
23
+ pool.release obj
24
+ end
25
+
26
+ it "can stop the pool" do
27
+ mock_server = mock(VncServer)
28
+
29
+ pool.stub(:running => [mock_server])
30
+ mock_server.should_receive(:stop)
31
+
32
+ pool.stop
33
+ end
34
+
35
+ it "raises a TooManyDisplaysError if the pool is over capacity" do
36
+ lambda { pool.release "foo" }.should raise_error(VncServerPool::TooManyDisplaysError)
37
+ end
38
+
39
+ it "raises a OutOfDisplaysError if the pool is empty" do
40
+ 3.times { pool.get }
41
+ lambda { pool.get }.should raise_error(VncServerPool::OutOfDisplaysError)
42
+ end
43
+
44
+ it "notifies observers" do
45
+ server = mock(VncServer, :start => nil, :stop => nil)
46
+ observer = mock(AbstractListener)
47
+
48
+ SpecHelper::FakeVnc.stub :new => server
49
+
50
+ observer.should_receive(:update).with :on_display_fetched , server
51
+ observer.should_receive(:update).with :on_display_released, server
52
+ observer.should_receive(:update).with :on_display_starting, server
53
+ observer.should_receive(:update).with :on_display_stopping , server
54
+
55
+ pool.add_observer observer
56
+
57
+ pool.release pool.get
58
+ pool.stop
59
+ end
60
+
61
+ end # VncServerPool
62
+ end # CukeForker
@@ -0,0 +1,48 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ module CukeForker
4
+ describe VncServer do
5
+
6
+ context "managing new displays" do
7
+ let(:server) { VncServer.new }
8
+
9
+ it "starts a new server" do
10
+ server.should_receive(:`).with("tightvncserver 2>&1").and_return("desktop is #{Socket.gethostname}:1")
11
+ server.start
12
+ server.display.should == ":1"
13
+ end
14
+
15
+ it "stops the server" do
16
+ server.should_receive(:`).with("tightvncserver -kill :5 2>&1")
17
+ server.stub :display => ":5"
18
+ server.stop
19
+ end
20
+
21
+ it "raises VncServer::Error if the server could not be started" do
22
+ server.should_receive(:`).and_return("oops")
23
+ server.stub :last_status => mock(:success? => false)
24
+
25
+ lambda { server.start }.should raise_error(VncServer::Error, /oops/)
26
+ end
27
+ end
28
+
29
+ context "controlling an existing display" do
30
+ let(:server) { VncServer.new ":5" }
31
+
32
+ it "starts the server on the given display" do
33
+ server.should_receive(:`).with("tightvncserver :5 2>&1").and_return("desktop is #{Socket.gethostname}:5")
34
+ server.start
35
+ server.display.should == ":5"
36
+ end
37
+ end
38
+
39
+ it "returns an instance for all existing displays" do
40
+ Dir.stub(:[]).and_return [".vnc/qa1:1.pid", ".vnc/qa1:2.pid", ".vnc/qa1:3.pid"]
41
+
42
+ all = VncServer.all
43
+ all.size.should == 3
44
+ all.map { |e| e.display }.should == [":1", ":2", ":3"]
45
+ end
46
+
47
+ end # VncServer
48
+ end # CukeForker