brynary-testjour 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +6 -0
- data/MIT-LICENSE.txt +19 -0
- data/README.rdoc +47 -0
- data/Rakefile +39 -0
- data/bin/testjour +8 -0
- data/lib/testjour/bonjour.rb +56 -0
- data/lib/testjour/cli.rb +78 -0
- data/lib/testjour/colorer.rb +8 -0
- data/lib/testjour/commands/base_command.rb +53 -0
- data/lib/testjour/commands/help.rb +20 -0
- data/lib/testjour/commands/list.rb +57 -0
- data/lib/testjour/commands/local_run.rb +83 -0
- data/lib/testjour/commands/run.rb +165 -0
- data/lib/testjour/commands/slave_run.rb +88 -0
- data/lib/testjour/commands/slave_start.rb +65 -0
- data/lib/testjour/commands/slave_stop.rb +25 -0
- data/lib/testjour/commands/slave_warm.rb +31 -0
- data/lib/testjour/commands/version.rb +18 -0
- data/lib/testjour/commands/warm.rb +73 -0
- data/lib/testjour/commands.rb +10 -0
- data/lib/testjour/cucumber_extensions/drb_formatter.rb +30 -0
- data/lib/testjour/cucumber_extensions/queueing_executor.rb +120 -0
- data/lib/testjour/mysql.rb +82 -0
- data/lib/testjour/pid_file.rb +43 -0
- data/lib/testjour/progressbar.rb +124 -0
- data/lib/testjour/queue_server.rb +66 -0
- data/lib/testjour/rsync.rb +29 -0
- data/lib/testjour/slave_server.rb +98 -0
- data/lib/testjour.rb +68 -0
- data/vendor/authprogs +231 -0
- metadata +102 -0
data/History.txt
ADDED
data/MIT-LICENSE.txt
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2008 Bryan Helmkamp
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
=== Testjour
|
2
|
+
|
3
|
+
* http://github.com/brynary/testjour
|
4
|
+
|
5
|
+
=== Description
|
6
|
+
|
7
|
+
Distributed test running with autodiscovery via Bonjour (for Cucumber first)
|
8
|
+
|
9
|
+
=== Synopsis
|
10
|
+
|
11
|
+
On machines to be used as Testjour slaves:
|
12
|
+
|
13
|
+
$ mkdir testjour-working-dir
|
14
|
+
$ testjour slave:start
|
15
|
+
|
16
|
+
|
17
|
+
On your development machine, verify it can see the testjour slave:
|
18
|
+
|
19
|
+
$ testjour list
|
20
|
+
|
21
|
+
Testjour servers:
|
22
|
+
|
23
|
+
bhelmkamp available bryans-computer.local.:62434
|
24
|
+
|
25
|
+
Now run your tests:
|
26
|
+
|
27
|
+
$ testjour run features
|
28
|
+
|
29
|
+
Note: This only really makes sense if you use more than one slave. Otherwise
|
30
|
+
it's slower than just running them locally.
|
31
|
+
|
32
|
+
=== Install
|
33
|
+
|
34
|
+
To install the latest release (once there is a release):
|
35
|
+
|
36
|
+
$ sudo gem install testjour
|
37
|
+
|
38
|
+
For now, just pull down the code from the GitHub repo:
|
39
|
+
|
40
|
+
$ git clone git://github.com/brynary/testjour.git
|
41
|
+
$ cd testjour
|
42
|
+
$ rake gem
|
43
|
+
$ rake install_gem
|
44
|
+
|
45
|
+
=== Authors
|
46
|
+
|
47
|
+
- Maintained by Bryan Helmkamp (http://brynary.com/)
|
data/Rakefile
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require "rake/gempackagetask"
|
3
|
+
require "rake/clean"
|
4
|
+
require './lib/testjour.rb'
|
5
|
+
|
6
|
+
spec = Gem::Specification.new do |s|
|
7
|
+
s.name = "testjour"
|
8
|
+
s.version = Testjour::VERSION
|
9
|
+
s.author = "Bryan Helmkamp"
|
10
|
+
s.email = "bryan" + "@" + "brynary.com"
|
11
|
+
s.homepage = "http://github.com/brynary/testjour"
|
12
|
+
s.summary = "Distributed test running with autodiscovery via Bonjour (for Cucumber first)"
|
13
|
+
s.description = s.summary
|
14
|
+
s.executables = "testjour"
|
15
|
+
s.files = %w[History.txt MIT-LICENSE.txt README.rdoc Rakefile] + Dir["bin/*"] + Dir["lib/**/*"] + Dir["vendor/**/*"]
|
16
|
+
|
17
|
+
s.add_dependency "systemu", ">=1.2.0"
|
18
|
+
s.add_dependency "dnssd", ">=0.6.0"
|
19
|
+
end
|
20
|
+
|
21
|
+
Rake::GemPackageTask.new(spec) do |package|
|
22
|
+
package.gem_spec = spec
|
23
|
+
end
|
24
|
+
|
25
|
+
desc 'Show information about the gem.'
|
26
|
+
task :write_gemspec do
|
27
|
+
File.open("testjour.gemspec", 'w') do |f|
|
28
|
+
f.write spec.to_ruby
|
29
|
+
end
|
30
|
+
puts "Generated: testjour.gemspec"
|
31
|
+
end
|
32
|
+
|
33
|
+
CLEAN.include ["pkg", "*.gem", "doc", "ri", "coverage"]
|
34
|
+
|
35
|
+
desc 'Install the package as a gem.'
|
36
|
+
task :install_gem => [:clean, :package] do
|
37
|
+
gem = Dir['pkg/*.gem'].first
|
38
|
+
sh "sudo gem install --local #{gem}"
|
39
|
+
end
|
data/bin/testjour
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require "dnssd"
|
2
|
+
require "set"
|
3
|
+
|
4
|
+
Thread.abort_on_exception = true
|
5
|
+
|
6
|
+
module Testjour
|
7
|
+
SERVICE = "_testjour._tcp"
|
8
|
+
|
9
|
+
class Bonjour
|
10
|
+
|
11
|
+
class Server
|
12
|
+
attr_reader :name, :host, :port
|
13
|
+
|
14
|
+
def initialize(name, host, port)
|
15
|
+
@name = name
|
16
|
+
@host = host
|
17
|
+
@port = port
|
18
|
+
end
|
19
|
+
|
20
|
+
def ==(other)
|
21
|
+
other.class == self.class && other.uri == self.uri
|
22
|
+
end
|
23
|
+
|
24
|
+
def uri
|
25
|
+
"druby://" + @host.gsub(/\.$/, "") + ":" + @port.to_s
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.list
|
30
|
+
hosts = []
|
31
|
+
|
32
|
+
service = DNSSD.browse(SERVICE) do |reply|
|
33
|
+
DNSSD.resolve(reply.name, reply.type, reply.domain) do |rr|
|
34
|
+
server = Server.new(reply.name, rr.target, rr.port)
|
35
|
+
hosts << server unless hosts.any? { |h| h == server }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
sleep 3
|
40
|
+
service.stop
|
41
|
+
return hosts
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.serve(port)
|
45
|
+
name = ENV['USER']
|
46
|
+
|
47
|
+
tr = DNSSD::TextRecord.new
|
48
|
+
tr['description'] = "#{name}'s testjour server"
|
49
|
+
|
50
|
+
DNSSD.register(name, SERVICE, "local", port, tr.encode) do |reply|
|
51
|
+
Testjour.logger.info "Broadcasting: Ready to run tests under name '#{name}' on port #{port}..."
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
data/lib/testjour/cli.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
module Testjour
|
2
|
+
module CLI
|
3
|
+
|
4
|
+
class NoCommandGiven < StandardError
|
5
|
+
def message
|
6
|
+
"No command given"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class UnknownCommand < StandardError
|
11
|
+
def initialize(command_name)
|
12
|
+
@command_name = command_name
|
13
|
+
end
|
14
|
+
|
15
|
+
def message
|
16
|
+
"Unknown command: #{@command_name.inspect}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.execute
|
21
|
+
Parser.new.execute
|
22
|
+
end
|
23
|
+
|
24
|
+
class Parser
|
25
|
+
class << self
|
26
|
+
attr_accessor :commands
|
27
|
+
|
28
|
+
def register_command(klass)
|
29
|
+
@commands << klass
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
self.commands = []
|
34
|
+
|
35
|
+
def execute
|
36
|
+
begin
|
37
|
+
raise NoCommandGiven if ARGV.empty?
|
38
|
+
raise UnknownCommand.new(command_name) unless command_klass
|
39
|
+
|
40
|
+
args = ARGV.dup
|
41
|
+
args.shift # Remove subcommand name
|
42
|
+
|
43
|
+
command_klass.new(self, args).run
|
44
|
+
rescue NoCommandGiven, UnknownCommand
|
45
|
+
$stderr.puts "ERROR: #{$!.message}"
|
46
|
+
$stderr.puts usage
|
47
|
+
exit 1
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def command_klass
|
52
|
+
self.class.commands.detect do |command_klass|
|
53
|
+
command_klass.command == command_name
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def command_name
|
58
|
+
ARGV.first
|
59
|
+
end
|
60
|
+
|
61
|
+
def usage
|
62
|
+
message = []
|
63
|
+
message << "usage: testjour <SUBCOMMAND> [OPTIONS] [ARGS...]"
|
64
|
+
message << "Type 'testjour help <SUBCOMMAND>' for help on a specific subcommand."
|
65
|
+
message << "Type 'testjour version' to get this program's version."
|
66
|
+
message << ""
|
67
|
+
message << "Available subcommands are:"
|
68
|
+
|
69
|
+
self.class.commands.sort_by { |c| c.command }.each do |command_klass|
|
70
|
+
message << " " + command_klass.command
|
71
|
+
end
|
72
|
+
|
73
|
+
message.map { |line| line.chomp }.join("\n")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require "optparse"
|
2
|
+
|
3
|
+
module Testjour
|
4
|
+
module CLI
|
5
|
+
|
6
|
+
class BaseCommand
|
7
|
+
attr_reader :non_options, :options
|
8
|
+
|
9
|
+
def self.command
|
10
|
+
self.name.downcase
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.options
|
14
|
+
{}
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.help
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.detailed_help
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
|
25
|
+
# def self.usage
|
26
|
+
# message = []
|
27
|
+
#
|
28
|
+
# if help.nil?
|
29
|
+
# message << command
|
30
|
+
# else
|
31
|
+
# message << "#{command}: #{help}"
|
32
|
+
# end
|
33
|
+
# message << detailed_help unless detailed_help.nil?
|
34
|
+
# message << ""
|
35
|
+
# message << "Valid options:"
|
36
|
+
# message
|
37
|
+
# @option_parser.summarize(message)
|
38
|
+
# end
|
39
|
+
|
40
|
+
def initialize(parser, args)
|
41
|
+
@parser = parser
|
42
|
+
@options = {}
|
43
|
+
@non_options = option_parser.parse(args)
|
44
|
+
end
|
45
|
+
|
46
|
+
def option_parser
|
47
|
+
OptionParser.new
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "testjour/commands/base_command"
|
2
|
+
|
3
|
+
module Testjour
|
4
|
+
module CLI
|
5
|
+
|
6
|
+
class HelpCommand < BaseCommand
|
7
|
+
def self.command
|
8
|
+
"help"
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
puts @parser.usage
|
13
|
+
exit 1
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
Parser.register_command HelpCommand
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require "drb"
|
2
|
+
require "testjour/commands/base_command"
|
3
|
+
require "testjour/bonjour"
|
4
|
+
|
5
|
+
module Testjour
|
6
|
+
module CLI
|
7
|
+
|
8
|
+
class List < BaseCommand
|
9
|
+
def self.command
|
10
|
+
"list"
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(*args)
|
14
|
+
super
|
15
|
+
Testjour.load_cucumber
|
16
|
+
require "testjour/colorer"
|
17
|
+
rescue LoadError
|
18
|
+
# No cucumber, we can't use color :(
|
19
|
+
end
|
20
|
+
|
21
|
+
def run
|
22
|
+
available_servers = Testjour::Bonjour.list
|
23
|
+
|
24
|
+
if available_servers.any?
|
25
|
+
puts
|
26
|
+
puts "Testjour servers:"
|
27
|
+
puts
|
28
|
+
|
29
|
+
available_servers.each do |server|
|
30
|
+
slave_server = DRbObject.new(nil, server.uri)
|
31
|
+
status = colorize_status(slave_server.status)
|
32
|
+
puts " %-12s %s %s" % [server.name, status, "#{server.host}:#{server.port}"]
|
33
|
+
end
|
34
|
+
else
|
35
|
+
puts
|
36
|
+
puts "No testjour servers found."
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def colorize_status(status)
|
41
|
+
formatted_status = ("%-12s" % status)
|
42
|
+
return formatted_status unless defined?(Testjour::Colorer)
|
43
|
+
|
44
|
+
case formatted_status.strip
|
45
|
+
when "available"
|
46
|
+
Testjour::Colorer.green(formatted_status)
|
47
|
+
else
|
48
|
+
Testjour::Colorer.yellow(formatted_status)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
Parser.register_command List
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require "drb"
|
2
|
+
require "uri"
|
3
|
+
|
4
|
+
require "testjour/commands/base_command"
|
5
|
+
require "testjour/queue_server"
|
6
|
+
require "testjour/cucumber_extensions/drb_formatter"
|
7
|
+
require "testjour/mysql"
|
8
|
+
|
9
|
+
module Testjour
|
10
|
+
module CLI
|
11
|
+
|
12
|
+
class LocalRun < BaseCommand
|
13
|
+
def self.command
|
14
|
+
"local:run"
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(parser, args)
|
18
|
+
Testjour.logger.debug "Runner command #{self.class}..."
|
19
|
+
super
|
20
|
+
@queue = @non_options.shift
|
21
|
+
end
|
22
|
+
|
23
|
+
def run
|
24
|
+
ARGV.clear # Don't pass along args to RSpec
|
25
|
+
Testjour.load_cucumber
|
26
|
+
|
27
|
+
ENV["RAILS_ENV"] = "test"
|
28
|
+
require File.expand_path("config/environment")
|
29
|
+
|
30
|
+
Testjour::MysqlDatabaseSetup.with_new_database do
|
31
|
+
Cucumber::CLI.executor.formatters = Testjour::DRbFormatter.new(queue_server)
|
32
|
+
require_files
|
33
|
+
|
34
|
+
begin
|
35
|
+
loop do
|
36
|
+
begin
|
37
|
+
run_file(queue_server.take_work)
|
38
|
+
rescue Testjour::QueueServer::NoWorkUnitsAvailableError
|
39
|
+
# If no work, ignore and keep looping
|
40
|
+
end
|
41
|
+
end
|
42
|
+
rescue DRb::DRbConnError
|
43
|
+
Testjour.logger.debug "DRb connection error. (This is normal.) Exiting runner."
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def require_files
|
49
|
+
cli = Cucumber::CLI.new
|
50
|
+
cli.parse_options!(@non_options)
|
51
|
+
cli.send(:require_files)
|
52
|
+
end
|
53
|
+
|
54
|
+
def run_file(file)
|
55
|
+
Testjour.logger.debug "Running feature file: #{file}"
|
56
|
+
features = feature_parser.parse_feature(File.expand_path(file))
|
57
|
+
Cucumber::CLI.executor.visit_features(features)
|
58
|
+
end
|
59
|
+
|
60
|
+
def queue_server
|
61
|
+
@queue_server ||= begin
|
62
|
+
DRb.start_service
|
63
|
+
DRbObject.new(nil, drb_uri)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def drb_uri
|
68
|
+
uri = URI.parse(@queue)
|
69
|
+
uri.scheme = "druby"
|
70
|
+
uri.path = ""
|
71
|
+
uri.user = nil
|
72
|
+
uri.to_s
|
73
|
+
end
|
74
|
+
|
75
|
+
def feature_parser
|
76
|
+
@feature_parser ||= Cucumber::TreetopParser::FeatureParser.new
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
Parser.register_command LocalRun
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require "drb"
|
2
|
+
|
3
|
+
require "testjour/commands/base_command"
|
4
|
+
require "testjour/queue_server"
|
5
|
+
require "testjour/bonjour"
|
6
|
+
|
7
|
+
module Testjour
|
8
|
+
module CLI
|
9
|
+
|
10
|
+
class Run < BaseCommand
|
11
|
+
def self.command
|
12
|
+
"run"
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(*args)
|
16
|
+
Testjour.logger.debug "Runner command #{self.class}..."
|
17
|
+
Testjour.load_cucumber
|
18
|
+
|
19
|
+
super
|
20
|
+
@found_server = 0
|
21
|
+
require "testjour/cucumber_extensions/queueing_executor"
|
22
|
+
require "testjour/colorer"
|
23
|
+
end
|
24
|
+
|
25
|
+
def run
|
26
|
+
Testjour::QueueServer.with_server do |queue|
|
27
|
+
start_local_runners unless servers_specified?
|
28
|
+
start_slave_runners unless no_remote?
|
29
|
+
|
30
|
+
if @found_server.zero?
|
31
|
+
puts "No processes to build on. Aborting."
|
32
|
+
exit
|
33
|
+
end
|
34
|
+
|
35
|
+
queue_features(queue)
|
36
|
+
print_results
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def disable_cucumber_require
|
41
|
+
Cucumber::CLI.class_eval do
|
42
|
+
def require_files
|
43
|
+
ARGV.clear # Shut up RSpec
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def queue_features(queue)
|
49
|
+
Testjour.logger.debug "Queueing features..."
|
50
|
+
disable_cucumber_require
|
51
|
+
ARGV.replace(@non_options.clone)
|
52
|
+
Cucumber::CLI.executor = Testjour::QueueingExecutor.new(queue, Cucumber::CLI.step_mother)
|
53
|
+
Cucumber::CLI.execute
|
54
|
+
end
|
55
|
+
|
56
|
+
def print_results
|
57
|
+
puts
|
58
|
+
puts "Requesting build from #{@found_server} processes..."
|
59
|
+
puts
|
60
|
+
|
61
|
+
Cucumber::CLI.executor.wait_for_results
|
62
|
+
Testjour.logger.debug "DONE"
|
63
|
+
end
|
64
|
+
|
65
|
+
def slave_servers_to_use
|
66
|
+
# require "rubygems"; require "ruby-debug"; Debugger.start; debugger
|
67
|
+
@slave_servers_to_use ||= available_servers.select do |potential_server|
|
68
|
+
!servers_specified? || specified_servers_include?(potential_server)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def available_servers
|
73
|
+
@available_servers ||= Testjour::Bonjour.list
|
74
|
+
end
|
75
|
+
|
76
|
+
def request_build_from(server)
|
77
|
+
slave_server = DRbObject.new(nil, server.uri)
|
78
|
+
result = slave_server.run(testjour_uri, @non_options)
|
79
|
+
|
80
|
+
if result
|
81
|
+
Testjour.logger.info "Requesting buld from available server: #{server.uri}. Accepted."
|
82
|
+
@found_server += 1
|
83
|
+
else
|
84
|
+
Testjour.logger.info "Requesting buld from available server: #{server.uri}. Rejected."
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def start_local_runners
|
89
|
+
2.times do
|
90
|
+
start_local_runner
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def start_slave_runners
|
95
|
+
slave_servers_to_use.each do |server|
|
96
|
+
request_build_from(server)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def start_local_runner
|
101
|
+
pid_queue = Queue.new
|
102
|
+
|
103
|
+
Thread.new do
|
104
|
+
Thread.current.abort_on_exception = true
|
105
|
+
cmd = command_for_local_run
|
106
|
+
Testjour.logger.debug "Starting local:run with command: #{cmd}"
|
107
|
+
status, stdout, stderr = systemu(cmd) { |pid| pid_queue << pid }
|
108
|
+
Testjour.logger.warn stderr if stderr.strip.size > 0
|
109
|
+
end
|
110
|
+
|
111
|
+
pid = pid_queue.pop
|
112
|
+
|
113
|
+
@found_server += 1
|
114
|
+
Testjour.logger.info "Started local:run on PID #{pid}"
|
115
|
+
|
116
|
+
pid
|
117
|
+
end
|
118
|
+
|
119
|
+
def command_for_local_run
|
120
|
+
"#{testjour_bin_path} local:run #{testjour_uri} -- #{@non_options.join(' ')}".strip
|
121
|
+
end
|
122
|
+
|
123
|
+
def testjour_bin_path
|
124
|
+
File.expand_path(File.dirname(__FILE__) + "/../../../bin/testjour")
|
125
|
+
end
|
126
|
+
|
127
|
+
def testjour_uri
|
128
|
+
uri = URI.parse(DRb.uri)
|
129
|
+
uri.path = File.expand_path(".")
|
130
|
+
uri.scheme = "testjour"
|
131
|
+
uri.user = `whoami`.strip
|
132
|
+
uri.to_s
|
133
|
+
end
|
134
|
+
|
135
|
+
def specified_servers_include?(potential_server)
|
136
|
+
@options[:server].any? do |specified_server|
|
137
|
+
potential_server.host.include?(specified_server)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def no_remote?
|
142
|
+
@options[:no_remote]
|
143
|
+
end
|
144
|
+
|
145
|
+
def servers_specified?
|
146
|
+
@options[:server] && @options[:server].any?
|
147
|
+
end
|
148
|
+
|
149
|
+
def option_parser
|
150
|
+
OptionParser.new do |opts|
|
151
|
+
opts.on("--on SERVER", "Specify a pattern to exclude servers to. Disabled local runners") do |server|
|
152
|
+
@options[:server] ||= []
|
153
|
+
@options[:server] << server
|
154
|
+
end
|
155
|
+
|
156
|
+
opts.on("--no-remote", "Only use local runners") do |server|
|
157
|
+
@options[:no_remote] = true
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
Parser.register_command Run
|
164
|
+
end
|
165
|
+
end
|