specjour 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|