deep_test_pre 2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. data/CHANGELOG +47 -0
  2. data/README.rdoc +199 -0
  3. data/Rakefile +137 -0
  4. data/lib/deep_test.rb +78 -0
  5. data/lib/deep_test/agent.rb +108 -0
  6. data/lib/deep_test/central_command.rb +165 -0
  7. data/lib/deep_test/cpu_info.rb +22 -0
  8. data/lib/deep_test/database/mysql_setup_listener.rb +112 -0
  9. data/lib/deep_test/database/setup_listener.rb +116 -0
  10. data/lib/deep_test/deadlock_detector.rb +7 -0
  11. data/lib/deep_test/demon.rb +25 -0
  12. data/lib/deep_test/distributed/beachhead.rb +104 -0
  13. data/lib/deep_test/distributed/dispatch_controller.rb +60 -0
  14. data/lib/deep_test/distributed/establish_beachhead.rb +19 -0
  15. data/lib/deep_test/distributed/filename_resolver.rb +40 -0
  16. data/lib/deep_test/distributed/landing_fleet.rb +30 -0
  17. data/lib/deep_test/distributed/landing_ship.rb +60 -0
  18. data/lib/deep_test/distributed/remote_deployment.rb +56 -0
  19. data/lib/deep_test/distributed/rsync.rb +50 -0
  20. data/lib/deep_test/distributed/shell_environment.rb +50 -0
  21. data/lib/deep_test/distributed/ssh_client_connection_info.rb +14 -0
  22. data/lib/deep_test/extensions/object_extension.rb +40 -0
  23. data/lib/deep_test/failure_message.rb +19 -0
  24. data/lib/deep_test/lib_root.rb +4 -0
  25. data/lib/deep_test/listener_list.rb +17 -0
  26. data/lib/deep_test/local_deployment.rb +46 -0
  27. data/lib/deep_test/logger.rb +32 -0
  28. data/lib/deep_test/main.rb +41 -0
  29. data/lib/deep_test/marshallable_exception_wrapper.rb +44 -0
  30. data/lib/deep_test/metrics/data.rb +34 -0
  31. data/lib/deep_test/metrics/measurement.rb +39 -0
  32. data/lib/deep_test/null_listener.rb +62 -0
  33. data/lib/deep_test/options.rb +113 -0
  34. data/lib/deep_test/proxy_io.rb +77 -0
  35. data/lib/deep_test/rake_tasks.rb +13 -0
  36. data/lib/deep_test/result_reader.rb +40 -0
  37. data/lib/deep_test/rspec_detector.rb +21 -0
  38. data/lib/deep_test/spec.rb +17 -0
  39. data/lib/deep_test/spec/extensions/example_group_methods.rb +64 -0
  40. data/lib/deep_test/spec/extensions/example_methods.rb +52 -0
  41. data/lib/deep_test/spec/extensions/options.rb +43 -0
  42. data/lib/deep_test/spec/extensions/spec_task.rb +21 -0
  43. data/lib/deep_test/spec/runner.rb +72 -0
  44. data/lib/deep_test/spec/work_result.rb +35 -0
  45. data/lib/deep_test/spec/work_unit.rb +59 -0
  46. data/lib/deep_test/test.rb +10 -0
  47. data/lib/deep_test/test/extensions/error.rb +14 -0
  48. data/lib/deep_test/test/run_test_suite.rb +5 -0
  49. data/lib/deep_test/test/runner.rb +24 -0
  50. data/lib/deep_test/test/supervised_test_suite.rb +48 -0
  51. data/lib/deep_test/test/work_result.rb +35 -0
  52. data/lib/deep_test/test/work_unit.rb +40 -0
  53. data/lib/deep_test/test_task.rb +47 -0
  54. data/lib/deep_test/ui/console.rb +74 -0
  55. data/lib/deep_test/ui/null.rb +17 -0
  56. data/lib/deep_test/warlock.rb +146 -0
  57. data/lib/telegraph.rb +29 -0
  58. data/lib/telegraph/ack_sequence.rb +14 -0
  59. data/lib/telegraph/logging.rb +20 -0
  60. data/lib/telegraph/message.rb +39 -0
  61. data/lib/telegraph/operator.rb +47 -0
  62. data/lib/telegraph/switchboard.rb +57 -0
  63. data/lib/telegraph/wire.rb +73 -0
  64. data/test/deep_test/agent_test.rb +175 -0
  65. data/test/deep_test/central_command_test.rb +147 -0
  66. data/test/deep_test/cpu_info_test.rb +33 -0
  67. data/test/deep_test/database/mysql_setup_listener_test.rb +18 -0
  68. data/test/deep_test/demon_test.rb +23 -0
  69. data/test/deep_test/distributed/beachhead_test.rb +67 -0
  70. data/test/deep_test/distributed/dispatch_controller_test.rb +162 -0
  71. data/test/deep_test/distributed/filename_resolver_test.rb +56 -0
  72. data/test/deep_test/distributed/landing_fleet_test.rb +55 -0
  73. data/test/deep_test/distributed/landing_ship_test.rb +48 -0
  74. data/test/deep_test/distributed/remote_deployment_test.rb +134 -0
  75. data/test/deep_test/distributed/rsync_test.rb +47 -0
  76. data/test/deep_test/distributed/shell_environment_test.rb +108 -0
  77. data/test/deep_test/distributed/ssh_client_connection_info_test.rb +34 -0
  78. data/test/deep_test/extensions/object_extension_test.rb +37 -0
  79. data/test/deep_test/listener_list_test.rb +22 -0
  80. data/test/deep_test/local_deployment_test.rb +19 -0
  81. data/test/deep_test/logger_test.rb +38 -0
  82. data/test/deep_test/main_test.rb +12 -0
  83. data/test/deep_test/marshallable_exception_wrapper_test.rb +46 -0
  84. data/test/deep_test/metrics/data_test.rb +22 -0
  85. data/test/deep_test/metrics/measurement_test.rb +18 -0
  86. data/test/deep_test/proxy_io_test.rb +104 -0
  87. data/test/deep_test/result_reader_test.rb +128 -0
  88. data/test/deep_test/test/extensions/error_test.rb +42 -0
  89. data/test/deep_test/test/runner_test.rb +11 -0
  90. data/test/deep_test/test/supervised_test_suite_test.rb +107 -0
  91. data/test/deep_test/test/work_result_test.rb +85 -0
  92. data/test/deep_test/test/work_unit_test.rb +63 -0
  93. data/test/deep_test/test_task_test.rb +15 -0
  94. data/test/deep_test/ui/console_test.rb +13 -0
  95. data/test/deep_test/warlock_test.rb +40 -0
  96. data/test/test_helper.rb +30 -0
  97. data/test/test_task_test.rb +75 -0
  98. metadata +156 -0
@@ -0,0 +1,17 @@
1
+ module DeepTest
2
+ module UI
3
+ class Null
4
+ def initialize(options)
5
+ end
6
+
7
+ def distributed_failover_to_local(method, exception)
8
+ end
9
+
10
+ def dispatch_starting(method_name)
11
+ end
12
+
13
+ def dispatch_finished(method_name)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,146 @@
1
+ module DeepTest
2
+ class Warlock
3
+ def initialize(options)
4
+ @options = options
5
+ @demons_semaphore = Mutex.new
6
+ @demons = []
7
+ @reapers = []
8
+ end
9
+
10
+ def start(name, demon, *demon_args)
11
+ # Not synchronizing for the fork seems to cause
12
+ # random errors (Bus Error, Segfault, and GC non-object)
13
+ # in Beachhead processes.
14
+ #
15
+ begin
16
+ pid = nil
17
+ @demons_semaphore.synchronize do
18
+ pid = fork do
19
+ # Fork leaves the semaphore locked and we'll never make it
20
+ # to end of synchronize block.
21
+ #
22
+ # The Ruby 1.8.6 C mutex implementation automatically treats
23
+ # a mutex locked by a dead thread as unlocked and will raise
24
+ # an error if we try to unlock it from this thread.
25
+ #
26
+ @demons_semaphore.unlock if @demons_semaphore.locked?
27
+
28
+ close_open_network_connections
29
+ demon.forked name, @options, demon_args
30
+
31
+ exit
32
+ end
33
+
34
+ raise "fatal: fork returned nil" if pid.nil?
35
+ add_demon name, pid
36
+ end
37
+
38
+ launch_reaper_thread name, pid
39
+
40
+ rescue => e
41
+ puts "exception starting #{name}: #{e}"
42
+ puts "\t" + e.backtrace.join("\n\t")
43
+ end
44
+ end
45
+
46
+ def close_open_network_connections
47
+ ObjectSpace.each_object(BasicSocket) do |sock|
48
+ begin
49
+ sock.close
50
+ rescue IOError
51
+ end
52
+ end
53
+ end
54
+
55
+ def demon_count
56
+ @demons_semaphore.synchronize do
57
+ @demons.size
58
+ end
59
+ end
60
+
61
+ def stop_demons
62
+ DeepTest.logger.debug { "stopping all demons" }
63
+ receivers = @demons_semaphore.synchronize do
64
+ @demons.reverse
65
+ end
66
+
67
+ receivers.reverse.each do |demon|
68
+ name, pid = demon
69
+ if running?(pid)
70
+ DeepTest.logger.debug { "Sending SIGTERM to #{name}, #{pid}" }
71
+ Process.kill("TERM", pid)
72
+ end
73
+ end
74
+ DeepTest.logger.debug { "Warlock: Stopped all receivers" }
75
+
76
+ DeepTest.logger.debug { "waiting for reapers" }
77
+ @reapers.each {|r| r.join}
78
+
79
+ DeepTest.logger.debug { "Warlock: done reaping processes" }
80
+ end
81
+
82
+ def exit_when_none_running
83
+ Thread.new do
84
+ wait_for_all_to_finish
85
+ DeepTest.logger.debug { "exiting #{Process.pid} with all demons finished" }
86
+ exit(0)
87
+ end
88
+ end
89
+
90
+ def wait_for_all_to_finish
91
+ loop do
92
+ Thread.pass
93
+ return unless any_running?
94
+ sleep(0.01)
95
+ end
96
+ end
97
+
98
+ def any_running?
99
+ @demons_semaphore.synchronize do
100
+ @demons.any? {|name, pid| running?(pid)}
101
+ end
102
+ end
103
+
104
+ #stolen from daemons
105
+ def running?(pid)
106
+ # Check if process is in existence
107
+ # The simplest way to do this is to send signal '0'
108
+ # (which is a single system call) that doesn't actually
109
+ # send a signal
110
+ begin
111
+ Process.kill(0, pid)
112
+ return true
113
+ rescue Errno::ESRCH
114
+ return false
115
+ rescue ::Exception # for example on EPERM (process exists but does not belong to us)
116
+ return true
117
+ #rescue Errno::EPERM
118
+ # return false
119
+ end
120
+ end
121
+
122
+ protected
123
+
124
+ def add_demon(name, pid)
125
+ DeepTest.logger.debug { "Started: #{name} (#{pid})" }
126
+ @demons << [name, pid]
127
+ end
128
+
129
+ def remove_demon(name, pid)
130
+ @demons.delete [name, pid]
131
+ DeepTest.logger.debug { "Stopped: #{name} (#{pid})" }
132
+ end
133
+
134
+
135
+ def launch_reaper_thread(name, pid)
136
+ @reapers << Thread.new do
137
+ Process.detach(pid).join
138
+ DeepTest.logger.debug { "#{name} (#{pid}) reaped" }
139
+ @demons_semaphore.synchronize do
140
+ DeepTest.logger.debug { "Warlock Reaper: removing #{name} (#{pid}) from demon list" }
141
+ remove_demon name, pid
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
data/lib/telegraph.rb ADDED
@@ -0,0 +1,29 @@
1
+ require 'socket'
2
+ require File.dirname(__FILE__) + "/telegraph/logging"
3
+ require File.dirname(__FILE__) + "/telegraph/ack_sequence"
4
+ require File.dirname(__FILE__) + "/telegraph/message"
5
+ require File.dirname(__FILE__) + "/telegraph/wire"
6
+ require File.dirname(__FILE__) + "/telegraph/operator"
7
+ require File.dirname(__FILE__) + "/telegraph/switchboard"
8
+
9
+ module Telegraph
10
+ class Ping
11
+ attr_reader :value
12
+
13
+ def initialize(value)
14
+ @value = value
15
+ end
16
+ end
17
+
18
+ class Pong
19
+ attr_reader :value
20
+
21
+ def initialize(value)
22
+ @value = value
23
+ end
24
+ end
25
+
26
+ class NoMessageAvailable < StandardError; end
27
+ class LineDead < StandardError; end
28
+ end
29
+
@@ -0,0 +1,14 @@
1
+ module Telegraph
2
+ class AckSequence
3
+ def initialize
4
+ @value = 0
5
+ end
6
+
7
+ def next
8
+ Thread.exclusive do
9
+ @value += 1
10
+ end
11
+ end
12
+ end
13
+ end
14
+
@@ -0,0 +1,20 @@
1
+ require 'logger'
2
+
3
+ module Telegraph
4
+ module Logging
5
+ def self.logger
6
+ @logger ||= begin
7
+ l = Logger.new($stdout)
8
+ l.level = Logger.const_get((ENV['TELEGRAPH_LOG_LEVEL'] || 'info').upcase)
9
+ l.formatter = proc do |sev, time, progmane, msg|
10
+ "[#{time.strftime "%T"}] (pid #{Process.pid}) #{msg}\n"
11
+ end
12
+ l
13
+ end
14
+ end
15
+
16
+ def debug
17
+ Logging.logger.debug { "#{self.class}: #{yield}" }
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,39 @@
1
+ module Telegraph
2
+ class Message
3
+ include Logging
4
+
5
+ attr_reader :body, :sequence_number, :sequence_ack
6
+
7
+ def initialize(body, sequence_number, sequence_ack)
8
+ @body = body
9
+ @sequence_number = sequence_number
10
+ @sequence_ack = sequence_ack
11
+ end
12
+
13
+ def write(stream)
14
+ message_data = Marshal.dump(body)
15
+ debug { "send #{message_data[4..20]}... (#{message_data.length} bytes)" }
16
+ stream.write [message_data.size, @sequence_number, @sequence_ack || 0].pack("NNN") + message_data
17
+ end
18
+
19
+ class <<self
20
+ include Logging
21
+
22
+ def read(stream)
23
+ header_data = read_data(stream, 12, "header")
24
+ size, sequence_number, sequence_ack = header_data.unpack("NNN")
25
+
26
+ message_data = read_data(stream, size, "message")
27
+ debug { "read #{message_data[4..20]}... (#{message_data.length} bytes)" }
28
+ Message.new Marshal.load(message_data), sequence_number, (sequence_ack == 0 ? nil : sequence_ack)
29
+ end
30
+
31
+ def read_data(stream, length, label)
32
+ data = stream.read(length)
33
+ raise IOError, "connection closed" unless data
34
+ raise IOError, "incomplete #{label} data" unless data.length == length
35
+ data
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,47 @@
1
+ require 'thread'
2
+
3
+ module Telegraph
4
+ class Operator
5
+ include Logging
6
+
7
+ attr_reader :switchboard
8
+
9
+ def self.listen(host, port, switchboard = Switchboard.new)
10
+ new TCPServer.new(host, port), switchboard
11
+ end
12
+
13
+ def initialize(socket, switchboard)
14
+ @socket = socket
15
+ @switchboard = switchboard
16
+ @accept_thread = Thread.new do
17
+ @socket.listen 100
18
+ loop do
19
+ if @should_shutdown
20
+ @socket.close
21
+ @switchboard.close_all_wires
22
+ break
23
+ end
24
+
25
+ begin
26
+ client = @socket.accept_nonblock
27
+ debug { "Accepted connection: #{client.inspect}" }
28
+ @switchboard.add_wire Wire.new(client)
29
+ rescue Errno::EAGAIN, Errno::ECONNABORTED, Errno::EPROTO, Errno::EINTR
30
+ connection_ready, = IO.select([@socket], nil, nil, 0.25)
31
+ retry if connection_ready
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ def port
38
+ @socket.addr[1]
39
+ end
40
+
41
+ def shutdown
42
+ debug { "Shutting down" }
43
+ @should_shutdown = true
44
+ @accept_thread.join
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,57 @@
1
+ module Telegraph
2
+ class Switchboard
3
+ include Logging
4
+
5
+ def process_messages(options = {:timeout => 0})
6
+ yield next_message(options) while true
7
+ rescue NoMessageAvailable
8
+ retry
9
+ end
10
+
11
+ def next_message(options = {:timeout => 0})
12
+ debug { "Waiting for next message on any of #{live_wires.size} wires for #{options[:timeout]} seconds" }
13
+
14
+ if live_wires.empty?
15
+ sleep 0.01
16
+ Thread.pass
17
+ raise NoMessageAvailable
18
+ end
19
+
20
+ readers, = IO.select live_wires.map {|w| w.stream}, nil, nil, options[:timeout]
21
+ raise NoMessageAvailable unless readers
22
+
23
+ wire = using_wires {|wires| wires.detect {|w| w.stream == readers.first} }
24
+ return wire.next_message(options.merge(:timeout => 0)), wire
25
+ rescue LineDead => e
26
+ debug { "LineDead: #{e.message} while reading message from wire" }
27
+ raise NoMessageAvailable
28
+ end
29
+
30
+ def any_live_wires?
31
+ live_wires.any?
32
+ end
33
+
34
+ def drop_wire(wire)
35
+ using_wires {|w| w.delete wire }
36
+ end
37
+
38
+ def add_wire(wire)
39
+ using_wires {|w| w << wire }
40
+ end
41
+
42
+ def close_all_wires
43
+ debug { "Closing all wires" }
44
+ using_wires {|w| w.each { |wire| wire.close rescue nil } }
45
+ end
46
+
47
+ def live_wires
48
+ using_wires {|w| w.select {|wire| !wire.closed?}}
49
+ end
50
+
51
+ def using_wires
52
+ @wires ||= []
53
+ @wires_mutex ||= Mutex.new
54
+ @wires_mutex.synchronize { yield @wires }
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,73 @@
1
+ module Telegraph
2
+ class Wire
3
+ include Logging
4
+
5
+ attr_reader :stream
6
+
7
+ def self.connect(host, port)
8
+ wire = new TCPSocket.new(host, port)
9
+ return wire unless block_given?
10
+ begin
11
+ yield wire
12
+ ensure
13
+ wire.close
14
+ end
15
+ end
16
+
17
+ def initialize(stream)
18
+ @sequence = AckSequence.new
19
+ @stream = stream
20
+ end
21
+
22
+ def close
23
+ if @stream.closed?
24
+ debug { "stream already closed" }
25
+ else
26
+ debug { "closing stream #{@stream.inspect}" }
27
+ @stream.close
28
+ end
29
+ end
30
+
31
+ def closed?
32
+ @stream.closed?
33
+ end
34
+
35
+ def send_message(body, options = {})
36
+ sequence_ack = options[:ack] ? options[:ack].sequence_number : nil
37
+ message = Message.new(body, @sequence.next, sequence_ack)
38
+ message.write stream
39
+ unacked_sequences_numbers[message.sequence_number] = message if options[:need_ack]
40
+ rescue IOError, Errno::EPIPE, Errno::ECONNRESET => e
41
+ close rescue nil
42
+ raise LineDead, e.message
43
+ end
44
+
45
+ def process_messages(options = {:timeout => 0})
46
+ yield next_message(options) while true
47
+ rescue NoMessageAvailable
48
+ retry
49
+ end
50
+
51
+ def next_message(options = {:timeout => 0})
52
+ begin
53
+ raise NoMessageAvailable unless IO.select [@stream], nil, nil, options[:timeout]
54
+ message = Message.read(@stream)
55
+ unacked_sequences_numbers.delete message.sequence_ack if message.sequence_ack
56
+ return message
57
+ rescue IOError, Errno::ECONNRESET => e
58
+ raise LineDead, e.message
59
+ end
60
+ rescue LineDead
61
+ close rescue nil
62
+ raise
63
+ end
64
+
65
+ def unacked_sequences_numbers
66
+ @unacked_sequences_numbers ||= {}
67
+ end
68
+
69
+ def unacked_messages
70
+ unacked_sequences_numbers.values
71
+ end
72
+ end
73
+ end