JonathanTron-specjour 0.2.5.1

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.
@@ -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