JonathanTron-specjour 0.2.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,85 @@
1
+ module Specjour
2
+ class Connection
3
+ include Protocol
4
+ extend Forwardable
5
+
6
+ attr_reader :uri
7
+ attr_writer :socket
8
+
9
+ def_delegators :socket, :flush, :closed?, :gets, :each
10
+
11
+ def self.wrap(established_connection)
12
+ host, port = established_connection.peeraddr.values_at(3,1)
13
+ connection = new URI::Generic.build(:host => host, :port => port)
14
+ connection.socket = established_connection
15
+ connection
16
+ end
17
+
18
+ def initialize(uri)
19
+ @uri = uri
20
+ end
21
+
22
+ alias to_str to_s
23
+
24
+ def connect
25
+ timeout { connect_socket }
26
+ end
27
+
28
+ def disconnect
29
+ socket.close
30
+ end
31
+
32
+ def socket
33
+ @socket ||= connect
34
+ end
35
+
36
+ def timeout(&block)
37
+ Timeout.timeout(2, &block)
38
+ rescue Timeout::Error
39
+ raise Error, "Connection to dispatcher timed out"
40
+ end
41
+
42
+ def next_test
43
+ will_reconnect do
44
+ send_message(:ready)
45
+ load_object socket.gets(TERMINATOR)
46
+ end
47
+ end
48
+
49
+ def print(arg)
50
+ will_reconnect do
51
+ socket.print dump_object(arg)
52
+ end
53
+ end
54
+
55
+ def puts(arg)
56
+ print(arg << "\n")
57
+ end
58
+
59
+ def send_message(method_name, *args)
60
+ print([method_name, *args])
61
+ flush
62
+ end
63
+
64
+ protected
65
+
66
+ def connect_socket
67
+ @socket = TCPSocket.open(uri.host, uri.port)
68
+ rescue Errno::ECONNREFUSED => error
69
+ Specjour.logger.debug "Could not connect to #{uri.to_s}\n#{error.inspect}"
70
+ retry
71
+ end
72
+
73
+ def reconnect
74
+ socket.close unless socket.closed?
75
+ connect
76
+ end
77
+
78
+ def will_reconnect(&block)
79
+ block.call
80
+ rescue SystemCallError => error
81
+ reconnect
82
+ retry
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,19 @@
1
+ module Specjour
2
+ module CPU
3
+ def self.cores
4
+ case RUBY_PLATFORM
5
+ when /darwin/
6
+ command('hostinfo') =~ /^(\d+).+logically/
7
+ $1.to_i
8
+ when /linux/
9
+ command('grep --count processor /proc/cpuinfo').to_i
10
+ end
11
+ end
12
+
13
+ protected
14
+
15
+ def self.command(cmd)
16
+ %x(#{cmd})
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,14 @@
1
+ module Specjour
2
+ module Cucumber
3
+ begin
4
+ require 'cucumber'
5
+ require 'cucumber/formatter/progress'
6
+
7
+ require 'specjour/cucumber/dispatcher'
8
+ require 'specjour/cucumber/distributed_formatter'
9
+ require 'specjour/cucumber/final_report'
10
+ require 'specjour/cucumber/printer'
11
+ rescue LoadError
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ module Specjour
2
+ module Cucumber
3
+ class Dispatcher < ::Specjour::Dispatcher
4
+
5
+ protected
6
+
7
+ def all_specs
8
+ @all_specs ||= Dir.chdir(project_path) do
9
+ Dir["features/**/*.feature"]
10
+ end
11
+ end
12
+
13
+ def printer
14
+ @printer ||= Printer.start(all_specs)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,84 @@
1
+ module Specjour::Cucumber
2
+ class DistributedFormatter < ::Cucumber::Formatter::Progress
3
+ class << self
4
+ attr_accessor :batch_size
5
+ end
6
+ @batch_size = 1
7
+
8
+ def initialize(step_mother, io, options)
9
+ @step_mother = step_mother
10
+ @io = io
11
+ @options = options
12
+ @failing_scenarios = []
13
+ @step_summary = []
14
+ end
15
+
16
+ def after_features(features)
17
+ print_summary
18
+ step_mother.scenarios.clear
19
+ step_mother.steps.clear
20
+ end
21
+
22
+ def prepare_failures
23
+ @failures = step_mother.scenarios(:failed).select { |s| s.is_a?(Cucumber::Ast::Scenario) }
24
+
25
+ if !@failures.empty?
26
+ @failures.each do |failure|
27
+ failure_message = ''
28
+ failure_message += format_string("cucumber " + failure.file_colon_line, :failed) +
29
+ failure_message += format_string(" # Scenario: " + failure.name, :comment)
30
+ @failing_scenarios << failure_message
31
+ end
32
+ end
33
+ end
34
+
35
+ def prepare_elements(elements, status, kind)
36
+ output = ''
37
+ if elements.any?
38
+ output += format_string("\n(::) #{status} #{kind} (::)\n", status)
39
+ output += "\n"
40
+ end
41
+
42
+ elements.each_with_index do |element, i|
43
+ if status == :failed
44
+ output += print_exception(element.exception, status, 0)
45
+ else
46
+ output += format_string(element.backtrace_line, status)
47
+ output += "\n"
48
+ end
49
+ @step_summary << output unless output.blank?
50
+ end
51
+ end
52
+
53
+ def prepare_steps(type)
54
+ prepare_elements(step_mother.scenarios(type), type, 'steps')
55
+ end
56
+
57
+ def print_exception(e, status, indent)
58
+ format_string("#{e.message} (#{e.class})\n#{e.backtrace.join("\n")}".indent(indent), status)
59
+ end
60
+
61
+ def print_summary
62
+ prepare_failures
63
+ prepare_steps(:failed)
64
+ prepare_steps(:undefined)
65
+
66
+ @io.send_message(:worker_summary=, to_hash)
67
+ end
68
+
69
+ OUTCOMES = [:failed, :skipped, :undefined, :pending, :passed]
70
+
71
+ def to_hash
72
+ hash = {}
73
+ [:scenarios, :steps].each do |type|
74
+ hash[type] = {}
75
+ OUTCOMES.each do |outcome|
76
+ hash[type][outcome] = step_mother.send(type, outcome).size
77
+ end
78
+ end
79
+ hash.merge!(:failing_scenarios => @failing_scenarios, :step_summary => @step_summary)
80
+ hash
81
+ end
82
+
83
+ end
84
+ end
@@ -0,0 +1,79 @@
1
+ module Specjour
2
+ module Cucumber
3
+ class Summarizer
4
+ attr_reader :duration, :failing_scenarios, :step_summary
5
+ def initialize
6
+ @duration = 0.0
7
+ @failing_scenarios = []
8
+ @step_summary = []
9
+ @scenarios = Hash.new(0)
10
+ @steps = Hash.new(0)
11
+ end
12
+
13
+ def increment(category, type, count)
14
+ current = instance_variable_get("@#{category}")
15
+ current[type] += count
16
+ end
17
+
18
+ def add(stats)
19
+ stats.each do |category, hash|
20
+ if category == :failing_scenarios
21
+ @failing_scenarios += hash
22
+ elsif category == :step_summary
23
+ @step_summary += hash
24
+ elsif category == :duration
25
+ @duration = hash.to_f if duration < hash.to_f
26
+ else
27
+ hash.each do |type, count|
28
+ increment(category, type, count)
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ def scenarios(status=nil)
35
+ length = status ? @scenarios[status] : @scenarios.inject(0) {|h,(k,v)| h += v}
36
+ any = @scenarios[status] > 0 if status
37
+ OpenStruct.new(:length => length , :any? => any)
38
+ end
39
+
40
+ def steps(status=nil)
41
+ length = status ? @steps[status] : @steps.inject(0) {|h,(k,v)| h += v}
42
+ any = @steps[status] > 0 if status
43
+ OpenStruct.new(:length => length , :any? => any)
44
+ end
45
+ end
46
+
47
+ class FinalReport
48
+ include ::Cucumber::Formatter::Console
49
+ def initialize
50
+ @features = []
51
+ @summarizer = Summarizer.new
52
+ end
53
+
54
+ def add(stats)
55
+ @summarizer.add(stats)
56
+ end
57
+
58
+ def exit_status
59
+ @summarizer.failing_scenarios.empty?
60
+ end
61
+
62
+ def summarize
63
+ if @summarizer.failing_scenarios.any?
64
+ puts "\n\n"
65
+ @summarizer.step_summary.each {|f| puts f }
66
+ puts "\n\n"
67
+ puts format_string("Failing Scenarios:", :failed)
68
+ @summarizer.failing_scenarios.each {|f| puts f }
69
+ end
70
+
71
+ default_format = lambda {|status_count, status| format_string(status_count, status)}
72
+ puts
73
+ puts scenario_summary(@summarizer, &default_format)
74
+ puts step_summary(@summarizer, &default_format)
75
+ puts format_duration(@summarizer.duration) if @summarizer.duration
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,9 @@
1
+ module Specjour
2
+ module Cucumber
3
+ class Printer < ::Specjour::Printer
4
+ def report
5
+ @report ||= FinalReport.new
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,47 @@
1
+ module Specjour
2
+ module DbScrub
3
+ require 'rake'
4
+ load 'tasks/misc.rake'
5
+ load 'tasks/databases.rake'
6
+
7
+ extend self
8
+
9
+ def scrub
10
+ connect_to_database
11
+ if pending_migrations?
12
+ puts "Migrating schema for database #{ENV['TEST_ENV_NUMBER']}..."
13
+ Rake::Task['db:test:load'].invoke
14
+ else
15
+ purge_tables
16
+ end
17
+ end
18
+
19
+ protected
20
+
21
+ def connect_to_database
22
+ connection
23
+ rescue # assume the database doesn't exist
24
+ Rake::Task['db:create'].invoke
25
+ end
26
+
27
+ def connection
28
+ ActiveRecord::Base.connection
29
+ end
30
+
31
+ def purge_tables
32
+ connection.disable_referential_integrity do
33
+ tables_to_purge.each do |table|
34
+ connection.delete "delete from #{table}"
35
+ end
36
+ end
37
+ end
38
+
39
+ def pending_migrations?
40
+ ActiveRecord::Migrator.new(:up, 'db/migrate').pending_migrations.any?
41
+ end
42
+
43
+ def tables_to_purge
44
+ connection.tables - ['schema_migrations']
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,119 @@
1
+ module Specjour
2
+ class Dispatcher
3
+ require 'dnssd'
4
+ Thread.abort_on_exception = true
5
+ include SocketHelpers
6
+
7
+ class << self
8
+ attr_accessor :interrupted
9
+ alias interrupted? interrupted
10
+ end
11
+
12
+ attr_reader :project_path, :managers, :manager_threads, :hosts
13
+ attr_accessor :worker_size
14
+
15
+ def initialize(project_path)
16
+ @project_path = project_path
17
+ @managers = []
18
+ @worker_size = 0
19
+ reset_manager_threads
20
+ end
21
+
22
+ def start
23
+ rsync_daemon.start
24
+ gather_managers
25
+ dispatch_work
26
+ printer.join
27
+ exit printer.exit_status
28
+ end
29
+
30
+ protected
31
+
32
+ def all_specs
33
+ @all_specs ||= Dir.chdir(project_path) do
34
+ Dir["spec/**/**/*_spec.rb"].sort
35
+ end
36
+ end
37
+
38
+ def command_managers(async = false, &block)
39
+ managers.each do |manager|
40
+ manager_threads << Thread.new(manager, &block)
41
+ end
42
+ wait_on_managers unless async
43
+ end
44
+
45
+ def dispatch_work
46
+ command_managers(true) { |m| m.dispatch }
47
+ end
48
+
49
+ def fetch_manager(uri)
50
+ Timeout.timeout(8) do
51
+ manager = DRbObject.new_with_uri(uri.to_s)
52
+ if !managers.include?(manager) && manager.available_for?(project_name)
53
+ set_up_manager(manager, uri)
54
+ managers << manager
55
+ self.worker_size += manager.worker_size
56
+ end
57
+ end
58
+ rescue Timeout::Error
59
+ Specjour.logger.debug "Couldn't work with manager at #{uri}"
60
+ end
61
+
62
+ def gather_managers
63
+ puts "Waiting for managers"
64
+ Signal.trap('INT') { self.class.interrupted = true; exit }
65
+ browser = DNSSD::Service.new
66
+ begin
67
+ Timeout.timeout(10) do
68
+ browser.browse '_druby._tcp' do |reply|
69
+ if reply.flags.add?
70
+ resolve_reply(reply)
71
+ end
72
+ browser.stop unless reply.flags.more_coming?
73
+ end
74
+ end
75
+ rescue Timeout::Error
76
+ end
77
+ puts "Managers found: #{managers.size}"
78
+ abort unless managers.size > 0
79
+ puts "Workers found: #{worker_size}"
80
+ printer.worker_size = worker_size
81
+ end
82
+
83
+ def printer
84
+ @printer ||= Printer.start(all_specs)
85
+ end
86
+
87
+ def project_name
88
+ @project_name ||= (ENV["SPECJOUR_PROJECT_NAME"] || File.basename(project_path))
89
+ end
90
+
91
+ def reset_manager_threads
92
+ @manager_threads = []
93
+ end
94
+
95
+ def resolve_reply(reply)
96
+ DNSSD.resolve!(reply) do |resolved|
97
+ resolved_ip = ip_from_hostname(resolved.target)
98
+ uri = URI::Generic.build :scheme => reply.service_name, :host => resolved_ip, :port => resolved.port
99
+ fetch_manager(uri)
100
+ resolved.service.stop if resolved.service.started?
101
+ end
102
+ end
103
+
104
+ def rsync_daemon
105
+ @rsync_daemon ||= RsyncDaemon.new(project_path, project_name)
106
+ end
107
+
108
+ def set_up_manager(manager, uri)
109
+ manager.project_name = project_name
110
+ manager.dispatcher_uri = URI::Generic.build :scheme => "specjour", :host => hostname, :port => printer.port
111
+ at_exit { manager.kill_worker_processes }
112
+ end
113
+
114
+ def wait_on_managers
115
+ manager_threads.each {|t| t.join; t.exit}
116
+ reset_manager_threads
117
+ end
118
+ end
119
+ end