specjour 0.4.1 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.markdown +35 -0
- data/README.markdown +9 -0
- data/Rakefile +4 -5
- data/lib/specjour/cli.rb +54 -30
- data/lib/specjour/configuration.rb +20 -8
- data/lib/specjour/connection.rb +4 -5
- data/lib/specjour/cpu.rb +5 -1
- data/lib/specjour/cucumber/preloader.rb +2 -1
- data/lib/specjour/cucumber.rb +0 -9
- data/lib/specjour/db_scrub.rb +8 -22
- data/lib/specjour/dispatcher.rb +33 -49
- data/lib/specjour/fork.rb +26 -0
- data/lib/specjour/loader.rb +129 -0
- data/lib/specjour/manager.rb +49 -33
- data/lib/specjour/printer.rb +70 -73
- data/lib/specjour/rspec/distributed_formatter.rb +1 -1
- data/lib/specjour/rspec/final_report.rb +2 -2
- data/lib/specjour/rspec/marshalable_exception.rb +4 -0
- data/lib/specjour/rspec/preloader.rb +10 -5
- data/lib/specjour/rspec/runner.rb +8 -7
- data/lib/specjour/rspec.rb +1 -6
- data/lib/specjour/rsync_daemon.rb +10 -7
- data/lib/specjour/worker.rb +5 -27
- data/lib/specjour.rb +15 -5
- metadata +70 -78
- data/lib/specjour/cucumber/main_ext.rb +0 -3
- data/lib/specjour/quiet_fork.rb +0 -11
data/History.markdown
CHANGED
@@ -1,6 +1,41 @@
|
|
1
1
|
History
|
2
2
|
=======
|
3
3
|
|
4
|
+
0.5.0 / 2012-02-20
|
5
|
+
----------------------
|
6
|
+
|
7
|
+
* [changed] Printer uses UNIX select instead of GServer (threads)
|
8
|
+
* [changed] Database is always dropped and reloaded using schema.rb or
|
9
|
+
structure.sql
|
10
|
+
* [removed] RSpec < 2.8 compatibility
|
11
|
+
* [added] Memory utilizing forks. No longer forking and execing means workers
|
12
|
+
start running tests faster.
|
13
|
+
* [added] Configuration.after_load hook; runs after loading the environment
|
14
|
+
* [added] Configurable rsync port
|
15
|
+
* [added] Specs distributed by example, not file! Means better
|
16
|
+
distribution/fast spec suites.
|
17
|
+
* [added] Rails compiled asset directory (tmp/cache) to the rsync inclusion
|
18
|
+
list. Workers won't have to compile assets during integration tests.
|
19
|
+
* [fixed] SQL structure files can be used to build the database.
|
20
|
+
* [fixed] Long timeout while waiting for bonjour requests. The bonjour code has
|
21
|
+
been rewritten.
|
22
|
+
* [fixed] Load specjour in its own environment when running bundle exec specjour
|
23
|
+
* [fixed] Forks running their parent's exit handlers.
|
24
|
+
* [fixed] Database creation when the app depends on a database upon environment
|
25
|
+
load (something as simple as a scope would cause this dependency). As long as
|
26
|
+
the regular test environment can be loaded, a worker without a database
|
27
|
+
shouldn't raise an exception, instead the db should be created.
|
28
|
+
|
29
|
+
[Full Changelog](https://github.com/sandro/specjour/compare/v0.4.1...0.5.0)
|
30
|
+
|
31
|
+
0.4.1 / 2011-06-17
|
32
|
+
------------------
|
33
|
+
|
34
|
+
l4rk and leshill
|
35
|
+
|
36
|
+
* [fixed] Cucumber failure reports not displayed
|
37
|
+
|
38
|
+
|
4
39
|
0.4.0 / 2011-03-09
|
5
40
|
------------------
|
6
41
|
|
data/README.markdown
CHANGED
@@ -102,6 +102,15 @@ By default, the dispatcher looks for managers matching the project's directory n
|
|
102
102
|
~/bizconf $ specjour listen -p bizconf_09
|
103
103
|
~/bizconf $ specjour -a bizconf_09
|
104
104
|
|
105
|
+
## Working with git
|
106
|
+
Commit the .specjour directory but ignore the performance file. The performance
|
107
|
+
file constantly changes, there's no need to commit it. Specjour uses it in an
|
108
|
+
attempt to optimize the run order; ensuring each machine gets at least one
|
109
|
+
long-running test.
|
110
|
+
|
111
|
+
$ cat .gitignore
|
112
|
+
/.specjour/performance
|
113
|
+
|
105
114
|
## Compatibility
|
106
115
|
|
107
116
|
* RSpec 2
|
data/Rakefile
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
require '
|
2
|
-
require 'rake'
|
1
|
+
require 'bundler/gem_tasks'
|
3
2
|
|
4
3
|
require 'rspec/core/rake_task'
|
5
4
|
RSpec::Core::RakeTask.new(:spec)
|
@@ -19,12 +18,12 @@ end
|
|
19
18
|
|
20
19
|
desc "tag, push gem, push to github"
|
21
20
|
task :prerelease do
|
22
|
-
|
21
|
+
require 'specjour'
|
23
22
|
command = %(
|
24
|
-
git tag v#{
|
23
|
+
git tag v#{Specjour::VERSION} &&
|
25
24
|
rake build &&
|
26
25
|
git push &&
|
27
|
-
gem push pkg/specjour-#{
|
26
|
+
gem push pkg/specjour-#{Specjour::VERSION}.gem &&
|
28
27
|
git push --tags
|
29
28
|
)
|
30
29
|
puts command
|
data/lib/specjour/cli.rb
CHANGED
@@ -10,25 +10,29 @@ module Specjour
|
|
10
10
|
method_option :alias, :aliases => "-a", :desc => "Project name advertised to listeners"
|
11
11
|
end
|
12
12
|
|
13
|
+
def self.rsync_port_option
|
14
|
+
method_option :rsync_port, :type => :numeric, :default => 23456, :desc => "Port to use for rsync daemon"
|
15
|
+
end
|
16
|
+
|
17
|
+
# allow specjour to be called with path arguments
|
13
18
|
def self.start(original_args=ARGV, config={})
|
14
|
-
real_tasks = all_tasks.keys |
|
19
|
+
real_tasks = all_tasks.keys | @map.keys
|
15
20
|
unless real_tasks.include? original_args.first
|
16
21
|
original_args.unshift default_task
|
17
22
|
end
|
18
23
|
super(original_args)
|
19
24
|
end
|
20
25
|
|
21
|
-
|
22
26
|
default_task :dispatch
|
23
27
|
|
24
28
|
class_option :log, :aliases => "-l", :type => :boolean, :desc => "Print debug messages to $stderr"
|
25
29
|
|
26
|
-
|
27
|
-
|
28
|
-
long_desc <<-D
|
30
|
+
desc "listen", "Listen for incoming tests to run"
|
31
|
+
long_desc <<-DESC
|
29
32
|
Advertise availability to run tests for the current directory.
|
30
|
-
|
33
|
+
DESC
|
31
34
|
worker_option
|
35
|
+
rsync_port_option
|
32
36
|
method_option :projects, :aliases => "-p", :type => :array, :desc => "Projects supported by this listener"
|
33
37
|
def listen
|
34
38
|
handle_logging
|
@@ -38,52 +42,66 @@ module Specjour
|
|
38
42
|
Specjour::Manager.new(args).start
|
39
43
|
end
|
40
44
|
|
41
|
-
desc "
|
45
|
+
desc "load", "load the app, then fork workers", :hide => true
|
46
|
+
worker_option
|
47
|
+
method_option :printer_uri, :required => true
|
48
|
+
method_option :project_path, :required => true
|
49
|
+
method_option :task, :required => true
|
50
|
+
method_option :test_paths, :type => :array, :default => []
|
51
|
+
method_option :quiet, :type => :boolean, :default => false
|
52
|
+
def load
|
53
|
+
handle_logging
|
54
|
+
handle_workers
|
55
|
+
append_to_program_name "load"
|
56
|
+
Specjour::Loader.new(args).start
|
57
|
+
end
|
58
|
+
|
59
|
+
desc "dispatch [test_paths]", "Send tests to a listener"
|
42
60
|
worker_option
|
43
61
|
dispatcher_option
|
44
|
-
|
62
|
+
rsync_port_option
|
63
|
+
long_desc <<-DESC
|
64
|
+
This is run when you simply type `specjour`.
|
65
|
+
By default, it will run the specs and features found in the current directory.
|
66
|
+
If you like, you can run a subset of tests by specifying the folder containing the tests.\n
|
67
|
+
Examples\n
|
68
|
+
`specjour dispatch spec`\n
|
69
|
+
`specjour dispatch features`\n
|
70
|
+
`specjour dispatch spec/models features/sign_up.feature`\n
|
71
|
+
DESC
|
72
|
+
def dispatch(*paths)
|
45
73
|
handle_logging
|
46
74
|
handle_workers
|
47
|
-
handle_dispatcher(
|
75
|
+
handle_dispatcher(paths)
|
48
76
|
append_to_program_name "dispatch"
|
49
77
|
Specjour::Dispatcher.new(args).start
|
50
78
|
end
|
51
79
|
|
52
|
-
desc "prepare [PROJECT_PATH]", "
|
53
|
-
long_desc <<-
|
80
|
+
desc "prepare [PROJECT_PATH]", "Run the prepare task on all listening workers"
|
81
|
+
long_desc <<-DESC
|
54
82
|
Run the Specjour::Configuration.prepare block on all listening workers.
|
55
|
-
Defaults to dropping
|
56
|
-
|
83
|
+
Defaults to dropping the database, then loading the schema.
|
84
|
+
DESC
|
57
85
|
worker_option
|
58
86
|
dispatcher_option
|
87
|
+
rsync_port_option
|
59
88
|
def prepare(path = Dir.pwd)
|
60
89
|
handle_logging
|
61
90
|
handle_workers
|
62
|
-
|
91
|
+
args[:project_path] = File.expand_path(path)
|
92
|
+
args[:project_alias] = args.delete(:alias)
|
93
|
+
args[:test_paths] = []
|
63
94
|
args[:worker_task] = 'prepare'
|
64
95
|
append_to_program_name "prepare"
|
65
96
|
Specjour::Dispatcher.new(args).start
|
66
97
|
end
|
67
98
|
|
99
|
+
map %w(-v --version) => :version
|
68
100
|
desc "version", "Show the current version"
|
69
101
|
def version
|
70
102
|
puts Specjour::VERSION
|
71
103
|
end
|
72
104
|
|
73
|
-
desc "work", "INTERNAL USE ONLY", :hide => true
|
74
|
-
method_option :project_path, :required => true
|
75
|
-
method_option :printer_uri, :required => true
|
76
|
-
method_option :number, :type => :numeric, :required => true
|
77
|
-
method_option :preload_spec
|
78
|
-
method_option :preload_feature
|
79
|
-
method_option :task, :required => true
|
80
|
-
method_option :quiet, :type => :boolean
|
81
|
-
def work
|
82
|
-
handle_logging
|
83
|
-
append_to_program_name "work"
|
84
|
-
Specjour::Worker.new(args).start
|
85
|
-
end
|
86
|
-
|
87
105
|
protected
|
88
106
|
|
89
107
|
def append_to_program_name(command)
|
@@ -102,9 +120,15 @@ module Specjour
|
|
102
120
|
args[:worker_size] = options["workers"] || CPU.cores
|
103
121
|
end
|
104
122
|
|
105
|
-
def handle_dispatcher(
|
106
|
-
|
123
|
+
def handle_dispatcher(paths)
|
124
|
+
if paths.empty?
|
125
|
+
args[:project_path] = Dir.pwd
|
126
|
+
else
|
127
|
+
args[:project_path] = File.expand_path(paths.first.sub(/(spec|features).*$/, ''))
|
128
|
+
end
|
129
|
+
args[:test_paths] = paths
|
107
130
|
args[:project_alias] = args.delete(:alias)
|
131
|
+
raise ArgumentError, "Cannot dispatch line numbers" if paths.any? {|p| p =~ /:\d+/}
|
108
132
|
end
|
109
133
|
end
|
110
134
|
end
|
@@ -2,18 +2,24 @@ module Specjour
|
|
2
2
|
module Configuration
|
3
3
|
extend self
|
4
4
|
|
5
|
-
attr_writer :before_fork, :after_fork, :prepare
|
5
|
+
attr_writer :before_fork, :after_fork, :after_load, :prepare
|
6
6
|
|
7
|
-
# This block is run by each worker
|
8
|
-
# The
|
9
|
-
#
|
10
|
-
# Set your own block if the default doesn't work for you.
|
7
|
+
# This block is run by each worker before they begin running tests.
|
8
|
+
# The default action is to migrate the database, and clear it of any old
|
9
|
+
# data.
|
11
10
|
def after_fork
|
12
11
|
@after_fork ||= default_after_fork
|
13
12
|
end
|
14
13
|
|
15
|
-
# This block is run after
|
16
|
-
#
|
14
|
+
# This block is run after the manager loads the app into memory, but before
|
15
|
+
# forking new worker processes. The default action is to disconnect from
|
16
|
+
# the ActiveRecord database.
|
17
|
+
def after_load
|
18
|
+
@after_load ||= default_after_load
|
19
|
+
end
|
20
|
+
|
21
|
+
# This block is run by the manager before forking workers. The default
|
22
|
+
# action is to run bundle install.
|
17
23
|
def before_fork
|
18
24
|
@before_fork ||= default_before_fork
|
19
25
|
end
|
@@ -29,6 +35,7 @@ module Specjour
|
|
29
35
|
def reset
|
30
36
|
@before_fork = nil
|
31
37
|
@after_fork = nil
|
38
|
+
@after_load = nil
|
32
39
|
@prepare = nil
|
33
40
|
end
|
34
41
|
|
@@ -40,7 +47,6 @@ module Specjour
|
|
40
47
|
|
41
48
|
def default_before_fork
|
42
49
|
lambda do
|
43
|
-
ActiveRecord::Base.remove_connection if defined?(ActiveRecord::Base)
|
44
50
|
bundle_install
|
45
51
|
end
|
46
52
|
end
|
@@ -51,6 +57,12 @@ module Specjour
|
|
51
57
|
end
|
52
58
|
end
|
53
59
|
|
60
|
+
def default_after_load
|
61
|
+
lambda do
|
62
|
+
ActiveRecord::Base.remove_connection if rails_with_ar?
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
54
66
|
def default_prepare
|
55
67
|
lambda do
|
56
68
|
if rails_with_ar?
|
data/lib/specjour/connection.rb
CHANGED
@@ -6,7 +6,7 @@ module Specjour
|
|
6
6
|
attr_reader :uri
|
7
7
|
attr_writer :socket
|
8
8
|
|
9
|
-
def_delegators :socket, :flush, :closed?, :gets, :each
|
9
|
+
def_delegators :socket, :flush, :close, :closed?, :gets, :each
|
10
10
|
|
11
11
|
def self.wrap(established_connection)
|
12
12
|
host, port = established_connection.peeraddr.values_at(3,1)
|
@@ -26,7 +26,7 @@ module Specjour
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def disconnect
|
29
|
-
socket.close
|
29
|
+
socket.close unless socket && socket.closed?
|
30
30
|
end
|
31
31
|
|
32
32
|
def socket
|
@@ -34,9 +34,8 @@ module Specjour
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def timeout(&block)
|
37
|
-
Timeout.timeout(
|
37
|
+
Timeout.timeout(0.5, &block)
|
38
38
|
rescue Timeout::Error
|
39
|
-
raise Error, "Connection to dispatcher timed out", []
|
40
39
|
end
|
41
40
|
|
42
41
|
def next_test
|
@@ -78,7 +77,7 @@ module Specjour
|
|
78
77
|
|
79
78
|
def will_reconnect(&block)
|
80
79
|
block.call
|
81
|
-
rescue SystemCallError => error
|
80
|
+
rescue SystemCallError, IOError => error
|
82
81
|
unless Specjour.interrupted?
|
83
82
|
reconnect
|
84
83
|
retry
|
data/lib/specjour/cpu.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Specjour
|
2
2
|
module CPU
|
3
3
|
def self.cores
|
4
|
-
case
|
4
|
+
case platform
|
5
5
|
when /darwin/
|
6
6
|
command('hostinfo') =~ /^(\d+).+physically/
|
7
7
|
$1.to_i
|
@@ -15,5 +15,9 @@ module Specjour
|
|
15
15
|
def self.command(cmd)
|
16
16
|
%x(#{cmd})
|
17
17
|
end
|
18
|
+
|
19
|
+
def self.platform
|
20
|
+
RUBY_PLATFORM
|
21
|
+
end
|
18
22
|
end
|
19
23
|
end
|
@@ -1,7 +1,8 @@
|
|
1
1
|
module Specjour
|
2
2
|
module Cucumber
|
3
3
|
module Preloader
|
4
|
-
def self.load
|
4
|
+
def self.load
|
5
|
+
require 'cucumber' unless defined?(::Cucumber::Cli)
|
5
6
|
configuration = ::Cucumber::Cli::Configuration.new
|
6
7
|
configuration.parse! []
|
7
8
|
runtime = ::Cucumber::Runtime.new(configuration)
|
data/lib/specjour/cucumber.rb
CHANGED
@@ -1,25 +1,16 @@
|
|
1
1
|
module Specjour
|
2
2
|
module Cucumber
|
3
3
|
begin
|
4
|
-
require 'cucumber'
|
5
4
|
require 'cucumber/formatter/progress'
|
6
5
|
|
7
6
|
require 'specjour/cucumber/distributed_formatter'
|
8
7
|
require 'specjour/cucumber/final_report'
|
9
8
|
require 'specjour/cucumber/preloader'
|
10
|
-
require 'specjour/cucumber/main_ext'
|
11
9
|
require 'specjour/cucumber/runner'
|
12
|
-
|
13
|
-
::Cucumber::Cli::Options.class_eval { def print_profile_information; end }
|
14
10
|
rescue LoadError
|
15
11
|
end
|
16
12
|
|
17
13
|
class << self; attr_accessor :runtime; end
|
18
14
|
|
19
|
-
def self.wants_to_quit
|
20
|
-
if defined?(::Cucumber) && ::Cucumber.respond_to?(:wants_to_quit=)
|
21
|
-
::Cucumber.wants_to_quit = true
|
22
|
-
end
|
23
|
-
end
|
24
15
|
end
|
25
16
|
end
|
data/lib/specjour/db_scrub.rb
CHANGED
@@ -1,17 +1,14 @@
|
|
1
|
+
# encoding: utf-8
|
1
2
|
module Specjour
|
2
3
|
module DbScrub
|
3
4
|
|
4
5
|
begin
|
5
6
|
require 'rake'
|
6
|
-
if defined?(
|
7
|
-
|
7
|
+
extend Rake::DSL if defined?(Rake::DSL)
|
8
|
+
if defined?(Rails)
|
9
|
+
Rake::Task.define_task(:environment) { }
|
8
10
|
load 'rails/tasks/misc.rake'
|
9
11
|
load 'active_record/railties/databases.rake'
|
10
|
-
else
|
11
|
-
load 'tasks/misc.rake'
|
12
|
-
load 'tasks/databases.rake'
|
13
|
-
Rake::Task["db:structure:dump"].clear
|
14
|
-
Rake::Task["environment"].clear
|
15
12
|
end
|
16
13
|
rescue LoadError
|
17
14
|
Specjour.logger.debug "Failed to load Rails rake tasks"
|
@@ -25,18 +22,15 @@ module Specjour
|
|
25
22
|
|
26
23
|
def scrub
|
27
24
|
connect_to_database
|
28
|
-
|
29
|
-
|
30
|
-
schema_load_task.invoke
|
31
|
-
else
|
32
|
-
purge_tables
|
33
|
-
end
|
25
|
+
puts "Resetting database #{ENV['TEST_ENV_NUMBER']}"
|
26
|
+
schema_load_task.invoke
|
34
27
|
end
|
35
28
|
|
36
29
|
protected
|
37
30
|
|
38
31
|
def connect_to_database
|
39
32
|
ActiveRecord::Base.remove_connection
|
33
|
+
ActiveRecord::Base.configurations = Rails.application.config.database_configuration
|
40
34
|
ActiveRecord::Base.establish_connection
|
41
35
|
connection
|
42
36
|
rescue # assume the database doesn't exist
|
@@ -47,20 +41,12 @@ module Specjour
|
|
47
41
|
ActiveRecord::Base.connection
|
48
42
|
end
|
49
43
|
|
50
|
-
def purge_tables
|
51
|
-
connection.disable_referential_integrity do
|
52
|
-
tables_to_purge.each do |table|
|
53
|
-
connection.delete "delete from #{table}"
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
44
|
def pending_migrations?
|
59
45
|
ActiveRecord::Migrator.new(:up, 'db/migrate').pending_migrations.any?
|
60
46
|
end
|
61
47
|
|
62
48
|
def schema_load_task
|
63
|
-
Rake::Task[{ :sql => "db:test:
|
49
|
+
Rake::Task[{ :sql => "db:test:load_structure", :ruby => "db:test:load" }[ActiveRecord::Base.schema_format]]
|
64
50
|
end
|
65
51
|
|
66
52
|
def tables_to_purge
|
data/lib/specjour/dispatcher.rb
CHANGED
@@ -4,18 +4,19 @@ module Specjour
|
|
4
4
|
Thread.abort_on_exception = true
|
5
5
|
include SocketHelper
|
6
6
|
|
7
|
-
attr_reader :project_alias, :managers, :manager_threads, :hosts, :options, :
|
7
|
+
attr_reader :project_alias, :managers, :manager_threads, :hosts, :options, :drb_connection_errors, :test_paths, :rsync_port
|
8
8
|
attr_accessor :worker_size, :project_path
|
9
9
|
|
10
10
|
def initialize(options = {})
|
11
11
|
Specjour.load_custom_hooks
|
12
12
|
@options = options
|
13
|
-
@project_path =
|
13
|
+
@project_path = options[:project_path]
|
14
|
+
@test_paths = options[:test_paths]
|
14
15
|
@worker_size = 0
|
15
16
|
@managers = []
|
16
17
|
@drb_connection_errors = Hash.new(0)
|
17
|
-
|
18
|
-
|
18
|
+
@rsync_port = options[:rsync_port]
|
19
|
+
reset_manager_threads
|
19
20
|
end
|
20
21
|
|
21
22
|
def start
|
@@ -23,41 +24,23 @@ module Specjour
|
|
23
24
|
gather_managers
|
24
25
|
rsync_daemon.start
|
25
26
|
dispatch_work
|
26
|
-
printer.
|
27
|
+
printer.start if dispatching_tests?
|
27
28
|
wait_on_managers
|
28
29
|
exit printer.exit_status
|
29
30
|
end
|
30
31
|
|
31
32
|
protected
|
32
33
|
|
33
|
-
def find_tests
|
34
|
-
if project_path.match(/(.+)\/((spec|features)(?:\/\w+)*)$/)
|
35
|
-
self.project_path = $1
|
36
|
-
@all_tests = $3 == 'spec' ? all_specs($2) : all_features($2)
|
37
|
-
else
|
38
|
-
@all_tests = all_specs | all_features
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def all_specs(tests_path = 'spec')
|
43
|
-
Dir[File.join(".", tests_path, "**/*_spec.rb")].sort
|
44
|
-
end
|
45
|
-
|
46
|
-
def all_features(tests_path = 'features')
|
47
|
-
Dir[File.join(".", tests_path, "**/*.feature")].sort
|
48
|
-
end
|
49
|
-
|
50
34
|
def add_manager(manager)
|
51
35
|
set_up_manager(manager)
|
52
36
|
managers << manager
|
53
37
|
self.worker_size += manager.worker_size
|
54
38
|
end
|
55
39
|
|
56
|
-
def command_managers(
|
40
|
+
def command_managers(&block)
|
57
41
|
managers.each do |manager|
|
58
42
|
manager_threads << Thread.new(manager, &block)
|
59
43
|
end
|
60
|
-
wait_on_managers unless async
|
61
44
|
end
|
62
45
|
|
63
46
|
def dispatcher_uri
|
@@ -69,8 +52,7 @@ module Specjour
|
|
69
52
|
managers.each do |manager|
|
70
53
|
puts "#{manager.hostname} (#{manager.worker_size})"
|
71
54
|
end
|
72
|
-
|
73
|
-
command_managers(true) { |m| m.dispatch rescue DRb::DRbConnError }
|
55
|
+
command_managers { |m| m.dispatch rescue DRb::DRbConnError }
|
74
56
|
end
|
75
57
|
|
76
58
|
def dispatching_tests?
|
@@ -85,12 +67,12 @@ module Specjour
|
|
85
67
|
rescue DRb::DRbConnError => e
|
86
68
|
drb_connection_errors[uri] += 1
|
87
69
|
Specjour.logger.debug "#{e.message}: couldn't connect to manager at #{uri}"
|
88
|
-
retry if drb_connection_errors[uri] < 5
|
70
|
+
sleep(0.1) && retry if drb_connection_errors[uri] < 5
|
89
71
|
end
|
90
72
|
|
91
73
|
def fork_local_manager
|
92
74
|
puts "No listeners found on this machine, starting one..."
|
93
|
-
manager_options = {:worker_size => options[:worker_size], :registered_projects => [project_alias]}
|
75
|
+
manager_options = {:worker_size => options[:worker_size], :registered_projects => [project_alias], :rsync_port => rsync_port}
|
94
76
|
manager = Manager.start_quietly manager_options
|
95
77
|
fetch_manager(manager.drb_uri)
|
96
78
|
at_exit do
|
@@ -101,23 +83,23 @@ module Specjour
|
|
101
83
|
end
|
102
84
|
|
103
85
|
def gather_managers
|
104
|
-
puts "Looking for
|
86
|
+
puts "Looking for listeners..."
|
105
87
|
gather_remote_managers
|
106
88
|
fork_local_manager if local_manager_needed?
|
107
|
-
abort "No
|
89
|
+
abort "No listeners found" if managers.size.zero?
|
108
90
|
end
|
109
91
|
|
110
92
|
def gather_remote_managers
|
111
|
-
|
112
|
-
Timeout.timeout(
|
113
|
-
|
114
|
-
if reply.flags.add?
|
115
|
-
|
116
|
-
end
|
117
|
-
browser.stop unless reply.flags.more_coming?
|
93
|
+
replies = []
|
94
|
+
Timeout.timeout(1) do
|
95
|
+
DNSSD.browse!('_druby._tcp') do |reply|
|
96
|
+
replies << reply if reply.flags.add?
|
97
|
+
break unless reply.flags.more_coming?
|
118
98
|
end
|
99
|
+
raise Timeout::Error
|
119
100
|
end
|
120
101
|
rescue Timeout::Error
|
102
|
+
replies.each {|r| resolve_reply(r)}
|
121
103
|
end
|
122
104
|
|
123
105
|
def local_manager_needed?
|
@@ -129,7 +111,7 @@ module Specjour
|
|
129
111
|
end
|
130
112
|
|
131
113
|
def printer
|
132
|
-
@printer ||= Printer.
|
114
|
+
@printer ||= Printer.new
|
133
115
|
end
|
134
116
|
|
135
117
|
def project_alias
|
@@ -140,34 +122,36 @@ module Specjour
|
|
140
122
|
@project_name ||= File.basename(project_path)
|
141
123
|
end
|
142
124
|
|
143
|
-
def
|
125
|
+
def reset_manager_threads
|
144
126
|
@manager_threads = []
|
145
127
|
end
|
146
128
|
|
147
129
|
def resolve_reply(reply)
|
148
|
-
DNSSD.resolve!(reply) do |resolved|
|
130
|
+
DNSSD.resolve!(reply.name, reply.type, reply.domain, flags=0, reply.interface) do |resolved|
|
149
131
|
Specjour.logger.debug "Bonjour discovered #{resolved.target}"
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
132
|
+
if resolved.text_record && resolved.text_record['version'] == Specjour::VERSION
|
133
|
+
resolved_ip = ip_from_hostname(resolved.target)
|
134
|
+
uri = URI::Generic.build :scheme => reply.service_name, :host => resolved_ip, :port => resolved.port
|
135
|
+
fetch_manager(uri)
|
136
|
+
else
|
137
|
+
puts "Found #{resolved.target} but its version doesn't match v#{Specjour::VERSION}. Skipping..."
|
138
|
+
end
|
139
|
+
break unless resolved.flags.more_coming?
|
154
140
|
end
|
155
141
|
end
|
156
142
|
|
157
143
|
def rsync_daemon
|
158
|
-
@rsync_daemon ||= RsyncDaemon.new(project_path, project_name)
|
144
|
+
@rsync_daemon ||= RsyncDaemon.new(project_path, project_name, rsync_port)
|
159
145
|
end
|
160
146
|
|
161
147
|
def set_up_manager(manager)
|
162
148
|
manager.project_name = project_name
|
163
149
|
manager.dispatcher_uri = dispatcher_uri
|
164
|
-
manager.
|
165
|
-
manager.preload_feature = all_tests.detect {|f| f =~ /\.feature$/}
|
150
|
+
manager.test_paths = test_paths
|
166
151
|
manager.worker_task = worker_task
|
167
152
|
at_exit do
|
168
153
|
begin
|
169
154
|
manager.interrupted = Specjour.interrupted?
|
170
|
-
manager.kill_worker_processes
|
171
155
|
rescue DRb::DRbConnError
|
172
156
|
end
|
173
157
|
end
|
@@ -175,7 +159,7 @@ module Specjour
|
|
175
159
|
|
176
160
|
def wait_on_managers
|
177
161
|
manager_threads.each {|t| t.join; t.exit}
|
178
|
-
|
162
|
+
reset_manager_threads
|
179
163
|
end
|
180
164
|
|
181
165
|
def worker_task
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Specjour::Fork
|
2
|
+
|
3
|
+
module_function
|
4
|
+
|
5
|
+
# fork, but don't run the parent's exit handlers
|
6
|
+
# The one exit handler we lose however, is the printing out
|
7
|
+
# of exceptions, so reincorporate that.
|
8
|
+
def fork
|
9
|
+
Kernel.fork do
|
10
|
+
at_exit { exit! }
|
11
|
+
begin
|
12
|
+
yield
|
13
|
+
rescue StandardError => e
|
14
|
+
$stderr.puts "#{e.class} #{e.message}", e.backtrace
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def fork_quietly
|
20
|
+
fork do
|
21
|
+
$stdout = StringIO.new
|
22
|
+
yield
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|