specjour 0.7.0 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/History.markdown +12 -0
  3. data/README.markdown +24 -1
  4. data/Rakefile +12 -12
  5. data/bin/specjour +3 -1
  6. data/lib/specjour/cli.rb +86 -110
  7. data/lib/specjour/colors.rb +23 -0
  8. data/lib/specjour/configuration.rb +47 -91
  9. data/lib/specjour/connection.rb +69 -20
  10. data/lib/specjour/cpu.rb +4 -0
  11. data/lib/specjour/fork.rb +1 -1
  12. data/lib/specjour/formatter.rb +153 -0
  13. data/lib/specjour/listener.rb +181 -0
  14. data/lib/specjour/loader.rb +55 -119
  15. data/lib/specjour/logger.rb +34 -0
  16. data/lib/specjour/plugin/base.rb +61 -0
  17. data/lib/specjour/plugin/manager.rb +28 -0
  18. data/lib/specjour/plugin/rails.rb +47 -0
  19. data/lib/specjour/plugin/rails_v3.rb +23 -0
  20. data/lib/specjour/plugin/rails_v4.rb +25 -0
  21. data/lib/specjour/plugin/rspec.rb +160 -0
  22. data/lib/specjour/plugin/rspec_v2.rb +53 -0
  23. data/lib/specjour/plugin/rspec_v3.rb +59 -0
  24. data/lib/specjour/plugin/ssh.rb +24 -0
  25. data/lib/specjour/plugin.rb +4 -0
  26. data/lib/specjour/printer.rb +235 -67
  27. data/lib/specjour/protocol.rb +13 -6
  28. data/lib/specjour/rspec_formatter.rb +17 -0
  29. data/lib/specjour/rsync_daemon.rb +6 -3
  30. data/lib/specjour/socket_helper.rb +26 -10
  31. data/lib/specjour/worker.rb +36 -62
  32. data/lib/specjour.rb +50 -24
  33. data/lib/specjour_plugin.rb +5 -0
  34. metadata +52 -84
  35. data/lib/specjour/cucumber/distributed_formatter.rb +0 -82
  36. data/lib/specjour/cucumber/final_report.rb +0 -83
  37. data/lib/specjour/cucumber/preloader.rb +0 -22
  38. data/lib/specjour/cucumber/runner.rb +0 -15
  39. data/lib/specjour/cucumber.rb +0 -16
  40. data/lib/specjour/db_scrub.rb +0 -56
  41. data/lib/specjour/dispatcher.rb +0 -170
  42. data/lib/specjour/manager.rb +0 -174
  43. data/lib/specjour/rspec/distributed_formatter.rb +0 -50
  44. data/lib/specjour/rspec/final_report.rb +0 -73
  45. data/lib/specjour/rspec/marshalable_exception.rb +0 -19
  46. data/lib/specjour/rspec/preloader.rb +0 -15
  47. data/lib/specjour/rspec/runner.rb +0 -14
  48. data/lib/specjour/rspec/shared_example_group_ext.rb +0 -9
  49. data/lib/specjour/rspec.rb +0 -17
@@ -1,170 +0,0 @@
1
- module Specjour
2
- class Dispatcher
3
- require 'dnssd'
4
- Thread.abort_on_exception = true
5
- include SocketHelper
6
-
7
- attr_reader :project_alias, :managers, :manager_threads, :hosts, :options, :drb_connection_errors, :test_paths, :rsync_port
8
- attr_accessor :worker_size, :project_path
9
-
10
- def initialize(options = {})
11
- Specjour.load_custom_hooks
12
- @options = options
13
- @project_path = options[:project_path]
14
- @test_paths = options[:test_paths]
15
- @worker_size = 0
16
- @managers = []
17
- @drb_connection_errors = Hash.new(0)
18
- @rsync_port = options[:rsync_port]
19
- @manager_threads = []
20
- end
21
-
22
- def start
23
- abort("#{project_path} doesn't exist") unless File.directory?(project_path)
24
- gather_managers
25
- rsync_daemon.start
26
- dispatch_work
27
- if dispatching_tests?
28
- printer.start
29
- else
30
- wait_on_managers
31
- end
32
- exit printer.exit_status
33
- end
34
-
35
- protected
36
-
37
- def add_manager(manager)
38
- set_up_manager(manager)
39
- managers << manager
40
- self.worker_size += manager.worker_size
41
- end
42
-
43
- def command_managers(&block)
44
- managers.each do |manager|
45
- manager_threads << Thread.new(manager, &block)
46
- end
47
- end
48
-
49
- def dispatcher_uri
50
- @dispatcher_uri ||= URI::Generic.build :scheme => "specjour", :host => local_ip, :port => printer.port
51
- end
52
-
53
- def dispatch_work
54
- puts "Workers found: #{worker_size}"
55
- managers.each do |manager|
56
- puts "#{manager.hostname} (#{manager.worker_size})"
57
- end
58
- command_managers { |m| m.dispatch rescue DRb::DRbConnError }
59
- end
60
-
61
- def dispatching_tests?
62
- worker_task == 'run_tests'
63
- end
64
-
65
- def fetch_manager(uri)
66
- manager = DRbObject.new_with_uri(uri.to_s)
67
- if !managers.include?(manager) && manager.available_for?(project_alias)
68
- add_manager(manager)
69
- end
70
- rescue DRb::DRbConnError => e
71
- drb_connection_errors[uri] += 1
72
- Specjour.logger.debug "#{e.message}: couldn't connect to manager at #{uri}"
73
- sleep(0.1) && retry if drb_connection_errors[uri] < 5
74
- end
75
-
76
- def fork_local_manager
77
- puts "No listeners found on this machine, starting one..."
78
- manager_options = {:worker_size => options[:worker_size], :registered_projects => [project_alias], :rsync_port => rsync_port}
79
- manager = Manager.start_quietly manager_options
80
- Process.detach manager.pid
81
- fetch_manager(manager.drb_uri)
82
- at_exit do
83
- unless Specjour.interrupted?
84
- Process.kill('TERM', manager.pid) rescue Errno::ESRCH
85
- end
86
- end
87
- end
88
-
89
- def gather_managers
90
- puts "Looking for listeners..."
91
- gather_remote_managers
92
- fork_local_manager if local_manager_needed?
93
- abort "No listeners found" if managers.size.zero?
94
- end
95
-
96
- def gather_remote_managers
97
- replies = []
98
- Timeout.timeout(1) do
99
- DNSSD.browse!('_druby._tcp') do |reply|
100
- replies << reply if reply.flags.add?
101
- end
102
- raise Timeout::Error
103
- end
104
- rescue Timeout::Error
105
- replies.each {|r| resolve_reply(r)}
106
- end
107
-
108
- def local_manager_needed?
109
- options[:worker_size] > 0 && no_local_managers?
110
- end
111
-
112
- def no_local_managers?
113
- managers.none? {|m| m.local_ip == local_ip}
114
- end
115
-
116
- def printer
117
- @printer ||= Printer.new
118
- end
119
-
120
- def project_alias
121
- @project_alias ||= options[:project_alias] || project_name
122
- end
123
-
124
- def project_name
125
- @project_name ||= File.basename(project_path)
126
- end
127
-
128
- def resolve_reply(reply)
129
- Timeout.timeout(1) do
130
- DNSSD.resolve!(reply.name, reply.type, reply.domain, flags=0, reply.interface) do |resolved|
131
- Specjour.logger.debug "Bonjour discovered #{resolved.target}"
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?
140
- end
141
- end
142
- rescue Timeout::Error
143
- end
144
-
145
- def rsync_daemon
146
- @rsync_daemon ||= RsyncDaemon.new(project_path, project_name, rsync_port)
147
- end
148
-
149
- def set_up_manager(manager)
150
- manager.project_name = project_name
151
- manager.dispatcher_uri = dispatcher_uri
152
- manager.test_paths = test_paths
153
- manager.worker_task = worker_task
154
- at_exit do
155
- begin
156
- manager.interrupted = Specjour.interrupted?
157
- rescue DRb::DRbConnError
158
- end
159
- end
160
- end
161
-
162
- def wait_on_managers
163
- manager_threads.each {|t| t.join; t.exit}
164
- end
165
-
166
- def worker_task
167
- options[:worker_task] || 'run_tests'
168
- end
169
- end
170
- end
@@ -1,174 +0,0 @@
1
- module Specjour
2
- class Manager
3
- require 'dnssd'
4
-
5
- include DRbUndumped
6
- include SocketHelper
7
- include Fork
8
-
9
- attr_accessor :test_paths, :project_name, :worker_task, :pid
10
- attr_reader :worker_size, :dispatcher_uri, :registered_projects, :loader_pid, :options, :rsync_port
11
-
12
- def self.start_quietly(options)
13
- manager = new options.merge(:quiet => true)
14
- manager.drb_uri
15
- manager.pid = Fork.fork_quietly { manager.start }
16
- manager
17
- end
18
-
19
- def initialize(options = {})
20
- @options = options
21
- @worker_size = options[:worker_size]
22
- @worker_task = options[:worker_task]
23
- @registered_projects = options[:registered_projects]
24
- @rsync_port = options[:rsync_port]
25
- Specjour.load_custom_hooks
26
- end
27
-
28
- def available_for?(project_name)
29
- registered_projects ? registered_projects.include?(project_name) : false
30
- end
31
-
32
- def dispatcher_uri=(uri)
33
- uri.host = ip_from_hostname(uri.host)
34
- @dispatcher_uri = uri
35
- end
36
-
37
- def dispatch
38
- suspend_bonjour do
39
- sync
40
- with_clean_env do
41
- execute_before_fork
42
- dispatch_loader
43
- end
44
- end
45
- end
46
-
47
- def drb_start
48
- $PROGRAM_NAME = "specjour listen" if quiet?
49
- DRb.start_service drb_uri.to_s, self
50
- at_exit { DRb.stop_service }
51
- end
52
-
53
- def drb_uri
54
- @drb_uri ||= begin
55
- current_uri.scheme = "druby"
56
- current_uri
57
- end
58
- end
59
-
60
- def dispatch_loader
61
- @loader_pid = fork do
62
- exec_cmd = "load --printer-uri #{dispatcher_uri} --workers #{worker_size} --task #{worker_task} --project-path #{project_path}"
63
- exec_cmd << " --test-paths #{test_paths.join(" ")}" if test_paths.any?
64
- exec_cmd << " --log" if Specjour.log?
65
- exec_cmd << " --quiet" if quiet?
66
- specjour_path = $LOAD_PATH.detect {|l| l =~ %r(specjour[^/]*/lib$)}
67
- bin_path = File.expand_path(File.join(specjour_path, "../bin"))
68
- Kernel.exec({"RUBYLIB" => $LOAD_PATH.join(":")}, "#{bin_path}/specjour #{exec_cmd}")
69
- end
70
- Process.waitall
71
- ensure
72
- kill_loader_process if loader_pid
73
- end
74
-
75
- def in_project(&block)
76
- Dir.chdir(project_path, &block)
77
- end
78
-
79
- def interrupted=(bool)
80
- Specjour.interrupted = bool
81
- kill_loader_process if loader_pid
82
- end
83
-
84
- def kill_loader_process
85
- if Specjour.interrupted?
86
- Process.kill('INT', loader_pid) rescue Errno::ESRCH
87
- else
88
- Process.kill('TERM', loader_pid) rescue Errno::ESRCH
89
- end
90
- @loader_pid = nil
91
- end
92
-
93
- def pid
94
- @pid || Process.pid
95
- end
96
-
97
- def project_path
98
- File.expand_path(project_name, File.realpath('/tmp'))
99
- end
100
-
101
- def start
102
- drb_start
103
- bonjour_announce
104
- at_exit { stop_bonjour }
105
- DRb.thread.join
106
- end
107
-
108
- def quiet?
109
- options.has_key? :quiet
110
- end
111
-
112
- def sync
113
- cmd "rsync #{Specjour::Configuration.rsync_options} --port=#{rsync_port} #{dispatcher_uri.host}::#{project_name} #{project_path}"
114
- end
115
-
116
- protected
117
-
118
- def bonjour_announce
119
- projects = registered_projects.join(", ")
120
- puts "Workers ready: #{worker_size}"
121
- puts "Listening for #{projects}"
122
- unless quiet?
123
- text = DNSSD::TextRecord.new
124
- text['version'] = Specjour::VERSION
125
- bonjour_service.register "specjour_manager_#{projects}_#{Process.pid}", "_#{drb_uri.scheme}._tcp", domain=nil, drb_uri.port, host=nil, text
126
- end
127
- end
128
-
129
- def bonjour_service
130
- @bonjour_service ||= DNSSD::Service.new
131
- end
132
-
133
- def cmd(command)
134
- Specjour.benchmark(command) do
135
- system *command.split
136
- end
137
- end
138
-
139
- def execute_before_fork
140
- Specjour.benchmark("before_fork") do
141
- in_project do
142
- Specjour.load_custom_hooks
143
- Configuration.before_fork.call
144
- end
145
- end
146
- end
147
-
148
- def stop_bonjour
149
- bonjour_service.stop if bonjour_service && !bonjour_service.stopped?
150
- @bonjour_service = nil
151
- end
152
-
153
- def suspend_bonjour(&block)
154
- stop_bonjour
155
- block.call
156
- bonjour_announce
157
- end
158
-
159
- def with_clean_env
160
- if defined?(Bundler)
161
- Bundler.with_clean_env do
162
- if ENV['RUBYOPT']
163
- opts = ENV['RUBYOPT'].split(" ").delete_if {|opt| opt =~ /bundler/}
164
- ENV['RUBYOPT'] = opts.join(" ")
165
- end
166
- yield
167
- end
168
- else
169
- yield
170
- end
171
- end
172
-
173
- end
174
- end
@@ -1,50 +0,0 @@
1
- module Specjour::RSpec
2
- class DistributedFormatter < Specjour::Configuration.rspec_formatter.call
3
-
4
- def metadata_for_examples
5
- examples.map do |example|
6
- metadata = example.metadata
7
- {
8
- :execution_result => marshalable_execution_result(example.execution_result),
9
- :description => metadata[:description],
10
- :file_path => metadata[:file_path],
11
- :full_description => metadata[:full_description],
12
- :line_number => metadata[:line_number],
13
- :location => metadata[:location]
14
- }
15
- end
16
- end
17
-
18
- def noop(*args)
19
- end
20
- alias dump_pending noop
21
- alias dump_failures noop
22
- alias start_dump noop
23
- alias message noop
24
-
25
- def color_enabled?
26
- true
27
- end
28
-
29
- def dump_summary(*args)
30
- output.send_message :rspec_summary=, metadata_for_examples
31
- end
32
-
33
- def close
34
- examples.clear
35
- super
36
- end
37
-
38
- protected
39
-
40
- def marshalable_execution_result(execution_result)
41
- if exception = execution_result[:exception]
42
- execution_result[:exception] = MarshalableException.new(exception)
43
- end
44
- execution_result[:started_at] = Time.at(execution_result[:started_at])
45
- execution_result[:finished_at] = Time.at(execution_result[:finished_at])
46
- execution_result
47
- end
48
-
49
- end
50
- end
@@ -1,73 +0,0 @@
1
- module Specjour::RSpec
2
- class FinalReport
3
- attr_reader :examples
4
- attr_reader :duration
5
-
6
- def initialize
7
- @examples = []
8
- @duration = 0.0
9
- ::RSpec.configuration.color_enabled = true
10
- ::RSpec.configuration.output_stream = $stdout
11
- end
12
-
13
- def add(data)
14
- if data.respond_to?(:has_key?) && data.has_key?(:duration)
15
- self.duration = data[:duration]
16
- else
17
- metadata_for_examples(data)
18
- end
19
- end
20
-
21
- def duration=(value)
22
- @duration = value.to_f if duration < value.to_f
23
- end
24
-
25
- def exit_status
26
- formatter.failed_examples.empty?
27
- end
28
-
29
- def metadata_for_examples(metadata_collection)
30
- examples.concat(
31
- metadata_collection.map do |partial_metadata|
32
- example = ::RSpec::Core::Example.allocate
33
- example.instance_variable_set(:@example_group_class,
34
- OpenStruct.new(:metadata => {}, :ancestors => [], :parent_groups => [])
35
- )
36
- metadata = ::RSpec::Core::Metadata.new
37
- metadata.merge! partial_metadata
38
- example.instance_variable_set(:@metadata, metadata)
39
- example
40
- end
41
- )
42
- end
43
-
44
- def pending_examples
45
- examples.select {|e| e.execution_result[:status] == 'pending'}
46
- end
47
-
48
- def failed_examples
49
- examples.select {|e| e.execution_result[:status] == 'failed'}
50
- end
51
-
52
- def formatter
53
- @formatter ||= new_progress_formatter
54
- end
55
-
56
- def summarize
57
- if examples.size > 0
58
- formatter.start_dump
59
- formatter.dump_pending
60
- formatter.dump_failures
61
- formatter.dump_summary(duration, examples.size, failed_examples.size, pending_examples.size)
62
- end
63
- end
64
-
65
- protected
66
- def new_progress_formatter
67
- new_formatter = ::RSpec::Core::Formatters::ProgressFormatter.new($stdout)
68
- new_formatter.instance_variable_set(:@failed_examples, failed_examples)
69
- new_formatter.instance_variable_set(:@pending_examples, pending_examples)
70
- new_formatter
71
- end
72
- end
73
- end
@@ -1,19 +0,0 @@
1
- module Specjour::RSpec
2
- class MarshalableException
3
- attr_accessor :message, :backtrace, :class_name
4
-
5
- def initialize(exception)
6
- self.class_name = exception.class.name
7
- self.message = exception.message
8
- self.backtrace = exception.backtrace
9
- end
10
-
11
- def class
12
- @class ||= OpenStruct.new :name => class_name
13
- end
14
-
15
- def pending_fixed?
16
- false
17
- end
18
- end
19
- end
@@ -1,15 +0,0 @@
1
- class Specjour::RSpec::Preloader
2
- def self.load(paths=[])
3
- Specjour.benchmark("Loading RSpec environment") do
4
- require File.expand_path('spec/spec_helper', Dir.pwd)
5
- load_spec_files paths
6
- end
7
- end
8
-
9
- def self.load_spec_files(paths)
10
- options = ::RSpec::Core::ConfigurationOptions.new(paths)
11
- options.parse_options
12
- options.configure ::RSpec.configuration
13
- ::RSpec.configuration.load_spec_files
14
- end
15
- end
@@ -1,14 +0,0 @@
1
- module Specjour::RSpec::Runner
2
- ::RSpec.configuration.backtrace_clean_patterns << %r(lib/specjour/)
3
-
4
- def self.run(spec, output)
5
- args = ['--format=Specjour::RSpec::DistributedFormatter', spec]
6
- ::RSpec::Core::Runner.run args, $stderr, output
7
- ensure
8
- ::RSpec.configuration.filter_manager = ::RSpec::Core::FilterManager.new
9
- ::RSpec.world.filtered_examples.clear
10
- ::RSpec.world.inclusion_filter.clear
11
- ::RSpec.world.exclusion_filter.clear
12
- ::RSpec.world.send(:instance_variable_set, :@line_numbers, nil)
13
- end
14
- end
@@ -1,9 +0,0 @@
1
- RSpec::Core::SharedExampleGroup.class_eval do
2
-
3
- def ensure_shared_example_group_name_not_taken(name)
4
- if RSpec.world.shared_example_groups.has_key?(name)
5
- Specjour.logger.debug "Shared example group '#{name}' already exists"
6
- end
7
- end
8
-
9
- end
@@ -1,17 +0,0 @@
1
- module Specjour
2
- module RSpec
3
- require 'rspec/core'
4
- require 'rspec/core/formatters/progress_formatter'
5
-
6
- require 'specjour/rspec/marshalable_exception'
7
- require 'specjour/rspec/preloader'
8
- require 'specjour/rspec/distributed_formatter'
9
- require 'specjour/rspec/final_report'
10
- require 'specjour/rspec/runner'
11
- require 'specjour/rspec/shared_example_group_ext'
12
-
13
- ::RSpec::Core::Runner.disable_autorun!
14
- ::RSpec::Core::Runner.class_eval "def self.trap_interrupt;end"
15
- ::RSpec.class_eval "def self.reset; world.reset; configuration.reset; end"
16
- end
17
- end