deep_test_pre 2.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.
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,108 @@
1
+ module DeepTest
2
+ class Agent
3
+ include Demon
4
+ attr_reader :number
5
+
6
+ def initialize(number, options, listener)
7
+ @number = number
8
+ @listener = listener
9
+ @options = options
10
+ end
11
+
12
+ def connect(stream_to_parent_process)
13
+ DeepTest.logger.debug { "Agent: Connecting to #{@options.origin_hostname}:#{@options.server_port}" }
14
+ @options.connect_to_central_command do |wire|
15
+ stream_to_parent_process.puts "Connected"
16
+ stream_to_parent_process.close rescue nil
17
+ yield wire
18
+ end
19
+ ensure
20
+ stream_to_parent_process.close unless stream_to_parent_process.closed?
21
+ end
22
+
23
+ def execute(stream_from_child_process, stream_to_parent_process)
24
+ stream_from_child_process.close
25
+ connect(stream_to_parent_process) do |wire|
26
+ reseed_random_numbers
27
+ reconnect_to_database
28
+
29
+ @listener.starting(self)
30
+ wire.send_message CentralCommand::NeedWork
31
+
32
+ while work_unit_message = next_work_unit_message(wire)
33
+ @listener.starting_work(self, work_unit_message.body)
34
+
35
+ result = begin
36
+ Metrics::Measurement.send_home("Agents Performing Work", wire, @options) do
37
+ work_unit_message.body.run
38
+ end
39
+ rescue Exception => error
40
+ Error.new(work_unit_message.body, error)
41
+ end
42
+
43
+ @listener.finished_work(self, work_unit_message.body, result)
44
+ send_result wire, work_unit_message, result
45
+ end
46
+ end
47
+ rescue CentralCommand::NoWorkUnitsRemainingError
48
+ DeepTest.logger.debug { "Agent #{number}: no more work to do" }
49
+ end
50
+
51
+ def next_work_unit_message(wire)
52
+ Metrics::Measurement.send_home("Agents Retrieving Work", wire, @options) do
53
+ begin
54
+ message = wire.next_message(:timeout => 2)
55
+ next message.body == CentralCommand::NoMoreWork ? nil : message
56
+ rescue Telegraph::NoMessageAvailable
57
+ DeepTest.logger.debug { "Agent: NoMessageAvailable" }
58
+ retry
59
+ rescue Telegraph::LineDead
60
+ DeepTest.logger.debug { "Agent: LineDead" }
61
+ next nil
62
+ end
63
+ end
64
+ end
65
+
66
+ def send_result(wire, work_unit_message, result)
67
+ wire.send_message result, :ack => work_unit_message
68
+ end
69
+
70
+ def reconnect_to_database
71
+ ActiveRecord::Base.connection.reconnect! if defined?(ActiveRecord::Base)
72
+ end
73
+
74
+ def reseed_random_numbers
75
+ srand
76
+ end
77
+
78
+
79
+ class Error
80
+ include CentralCommand::Result
81
+
82
+ attr_accessor :work_unit, :error
83
+
84
+ def initialize(work_unit, error)
85
+ @work_unit, @error = work_unit, error
86
+ end
87
+
88
+ def _dump(limit)
89
+ Marshal.dump([@work_unit, @error], limit)
90
+ rescue
91
+ Marshal.dump(["<< Undumpable >>", @error], limit)
92
+ end
93
+
94
+ def self._load(string)
95
+ new *Marshal.load(string)
96
+ end
97
+
98
+ def ==(other)
99
+ work_unit == other.work_unit &&
100
+ error == other.error
101
+ end
102
+
103
+ def to_s
104
+ "#{@work_unit}: #{@error}\n" + (@error.backtrace || []).join("\n")
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,165 @@
1
+ require 'set'
2
+
3
+ module DeepTest
4
+ class CentralCommand
5
+ attr_reader :operator
6
+ attr_reader :switchboard
7
+ attr_reader :data
8
+
9
+ def initialize(options)
10
+ @options = options
11
+ @work_queue = Queue.new
12
+ @results_mutex = Mutex.new
13
+ @results_condvar = ConditionVariable.new
14
+ @results = []
15
+ @data = Metrics::Data.new
16
+ end
17
+
18
+ def done_with_work
19
+ @done_with_work = true
20
+ end
21
+
22
+ def take_result
23
+ @results_mutex.synchronize do
24
+ loop do
25
+ if @results.any?
26
+ return @results.shift
27
+ else
28
+ @results_condvar.wait @results_mutex
29
+ raise NoAgentsRunningError unless @results.any? || @switchboard.any_live_wires?
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ def take_work
36
+ @work_queue.pop(true)
37
+ rescue ThreadError => e
38
+ if e.message == "queue empty"
39
+ raise NoWorkUnitsRemainingError if @done_with_work
40
+ raise NoWorkUnitsAvailableError
41
+ else
42
+ raise
43
+ end
44
+ end
45
+
46
+ def write_result(result)
47
+ @results_mutex.synchronize do
48
+ @results << result
49
+ @results_condvar.signal
50
+ end
51
+ end
52
+
53
+ def write_work(work_unit)
54
+ @work_queue.push work_unit
55
+ end
56
+
57
+ def stdout
58
+ $stdout
59
+ end
60
+
61
+ def stderr
62
+ $stderr
63
+ end
64
+
65
+ def self.start(options)
66
+ central_command = new(options)
67
+ central_command.start
68
+ central_command
69
+ end
70
+
71
+ def start
72
+ @switchboard = Telegraph::Switchboard.new
73
+ @operator = Telegraph::Operator.listen("0.0.0.0", 0, @switchboard)
74
+ @options.server_port = @operator.port
75
+ @process_messages_thread = Thread.new { process_messages }
76
+
77
+ DeepTest.logger.info { "Started DeepTest service on port #{@operator.port}" }
78
+ end
79
+
80
+ unless defined?(NeedWork)
81
+ NeedWork = "NeedWork"
82
+ NoMoreWork = "NoMoreWork"
83
+ module Result; end
84
+ module Operation; end
85
+ end
86
+
87
+ def process_messages
88
+ loop do
89
+ begin
90
+ return if @stop_process_messages
91
+ wires_waiting_for_work.each { |w| send_work w }
92
+
93
+ @results_mutex.synchronize do
94
+ # make take_result wake up and check if any agents are running
95
+ @results_condvar.signal
96
+ end
97
+
98
+ message, wire = switchboard.next_message(:timeout => 0.5)
99
+
100
+ case message.body
101
+ when NeedWork
102
+ send_work wire
103
+ when Result
104
+ write_result message.body
105
+ send_work wire
106
+ when Operation
107
+ message.body.execute
108
+ when Metrics::Measurement
109
+ data.add message.body
110
+ else
111
+ raise UnexpectedMessageError, message.inspect
112
+ end
113
+
114
+ rescue Telegraph::NoMessageAvailable
115
+ retry
116
+ rescue Exception => e
117
+ raise unless @stop_process_messages
118
+ end
119
+ end
120
+ end
121
+
122
+ def wires_waiting_for_work
123
+ @wires_waiting_for_work ||= Set.new
124
+ end
125
+
126
+ def send_work(wire)
127
+ begin
128
+ wire.send_message take_work, :need_ack => true
129
+ wires_waiting_for_work.delete wire
130
+
131
+ rescue NoWorkUnitsAvailableError
132
+ put_abandonded_work_back_on_the_queue
133
+ wires_waiting_for_work.add wire
134
+
135
+ rescue NoWorkUnitsRemainingError
136
+ wire.send_message NoMoreWork
137
+ wires_waiting_for_work.delete wire
138
+
139
+ end
140
+ end
141
+
142
+ def put_abandonded_work_back_on_the_queue
143
+ closed_wires = switchboard.using_wires { |wires| wires.select {|w| w.closed? }}
144
+ closed_wires.each do |wire|
145
+ wire.unacked_messages.each do |m|
146
+ write_work m.body
147
+ switchboard.drop_wire wire
148
+ end
149
+ end
150
+ end
151
+
152
+ def stop
153
+ @stop_process_messages = true
154
+ operator.shutdown
155
+ @process_messages_thread.join
156
+ data.save @options.metrics_file if @options.gathering_metrics?
157
+ end
158
+
159
+ class NoWorkUnitsAvailableError < StandardError; end
160
+ class NoWorkUnitsRemainingError < StandardError; end
161
+ class NoAgentsRunningError < StandardError; end
162
+ class CheckIfAgentsAreStillRunning < StandardError; end
163
+ class UnexpectedMessageError < StandardError; end
164
+ end
165
+ end
@@ -0,0 +1,22 @@
1
+ module DeepTest
2
+ class CpuInfo
3
+ attr_accessor :platform
4
+
5
+ def initialize(platform = RUBY_PLATFORM)
6
+ @platform = platform
7
+ end
8
+
9
+ def count
10
+ case platform
11
+ when /darwin/
12
+ output = `sysctl -n hw.ncpu`
13
+ output.strip.to_i
14
+ when /linux/
15
+ File.readlines("/proc/cpuinfo").inject(0) do |count, line|
16
+ next count + 1 if line =~ /processor\s*:\s*\d+/
17
+ count
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,112 @@
1
+ module DeepTest
2
+ module Database
3
+ #
4
+ # SetupListener implementation for MySQL.
5
+ #
6
+ class MysqlSetupListener < SetupListener
7
+ class <<self
8
+ #
9
+ # ActiveRecord configuration to use when connecting to
10
+ # MySQL to create databases, drop database, and grant
11
+ # privileges. By default, connects to information_schema
12
+ # on localhost as root with no password.
13
+ #
14
+ attr_accessor :admin_configuration
15
+ end
16
+ self.admin_configuration = {
17
+ :adapter => "mysql",
18
+ :host => "localhost",
19
+ :username => "root",
20
+ :database => "information_schema"
21
+ }
22
+
23
+ #
24
+ # Creates database and grants privileges (via +grant_privileges+)
25
+ # on it via ActiveRecord connection based on admin_configuration.
26
+ #
27
+ def create_database
28
+ admin_connection do |connection|
29
+ connection.create_database agent_database
30
+ grant_privileges connection
31
+ end
32
+ end
33
+
34
+ #
35
+ # Grants 'all' privilege on agent database to username and password
36
+ # specified by agent database config. If your application has
37
+ # special database privilege needs beyond 'all', you should override
38
+ # this method and grant them.
39
+ #
40
+ def grant_privileges(connection)
41
+ identified_by = if agent_database_config[:password]
42
+ %{identified by %s} % connection.quote(agent_database_config[:password])
43
+ else
44
+ ""
45
+ end
46
+ sql = %{grant all on #{agent_database}.* to %s@'localhost' #{identified_by} ; } %
47
+ connection.quote(agent_database_config[:username])
48
+
49
+ connection.execute sql
50
+ end
51
+
52
+ #
53
+ # Drops database via ActiveRecord connection based on admin_configuration
54
+ #
55
+ def drop_database
56
+ admin_connection do |connection|
57
+ connection.drop_database agent_database
58
+ end
59
+ end
60
+
61
+ #
62
+ # Dumps schema from master database using mysqldump command
63
+ #
64
+ def dump_schema
65
+ config = command_line_config(master_database_config)
66
+ system "mysqldump -R #{config} > #{dump_file_name}"
67
+ raise "Error Dumping schema" unless $?.success?
68
+ end
69
+
70
+ #
71
+ # Loads dumpfile into agent database using mysql command
72
+ #
73
+ def load_schema
74
+ config = command_line_config(agent_database_config)
75
+ system "mysql #{config} < #{dump_file_name}"
76
+ raise "Error Loading schema" unless $?.success?
77
+ end
78
+
79
+ #
80
+ # Location to store dumpfile. The default assumes you are testing
81
+ # a Rails project. You should override this if you are not using Rails
82
+ # or would like the dump file to be something other than the default
83
+ #
84
+ def dump_file_name
85
+ "#{RAILS_ROOT}/db/deep_test_schema.sql"
86
+ end
87
+
88
+ def system(command) # :nodoc:
89
+ DeepTest.logger.info { command }
90
+ super command
91
+ end
92
+
93
+ def command_line_config(config) # :nodoc:
94
+ command = ['-u', config[:username]]
95
+ command += ["-p#{config[:password]}"] if config[:password]
96
+ command += ['-h', config[:host]] if config[:host]
97
+ command += ['-P', config[:port]] if config[:port]
98
+ command += ['-S', config[:socket]] if config[:socket]
99
+ command += [config[:database]]
100
+ command.join(' ')
101
+ end
102
+
103
+ def admin_connection # :nodoc:
104
+ conn = ActiveRecord::Base.mysql_connection(self.class.admin_configuration)
105
+ yield conn
106
+ ensure
107
+ conn.disconnect! if conn
108
+ end
109
+ end
110
+ end
111
+ end
112
+
@@ -0,0 +1,116 @@
1
+ module DeepTest
2
+ module Database
3
+ #
4
+ # Skeleton Listener to help with setting up a separate database
5
+ # for each agent. Calls +dump_schema+, +load_schema+, +create_database+,
6
+ # and +drop_database+ hooks provided by subclasses that implement database
7
+ # setup strategies for particular database flavors.
8
+ #
9
+ class SetupListener < NullListener
10
+ DUMPED_SCHEMAS = [] unless defined?(DUMPED_SCHEMAS)
11
+
12
+ def before_sync # :nodoc:
13
+ dump_schema_once
14
+ end
15
+
16
+ def before_starting_agents # :nodoc:
17
+ dump_schema_once
18
+ end
19
+
20
+ def dump_schema_once # :nodoc:
21
+ schema_name = master_database_config[:database]
22
+ dump_schema unless DUMPED_SCHEMAS.include?(schema_name)
23
+ DUMPED_SCHEMAS << schema_name
24
+ end
25
+
26
+ def starting(agent) # :nodoc:
27
+ @agent = agent
28
+
29
+ at_exit do
30
+ DeepTest.logger.debug { "dropping database #{agent_database}" }
31
+ drop_database
32
+ end
33
+
34
+ drop_database
35
+ create_database
36
+ connect_to_database
37
+ load_schema
38
+ end
39
+
40
+ #
41
+ # Called on each agent after creating database and before loading
42
+ # schema to initialize connections
43
+ #
44
+ def connect_to_database
45
+ ActiveRecord::Base.establish_connection(agent_database_config)
46
+ end
47
+
48
+ #
49
+ # Called in each agent to create the database named by
50
+ # +agent_database+.
51
+ #
52
+ def create_database
53
+ raise "Subclass must implement"
54
+ end
55
+
56
+ #
57
+ # Called in each agent to drop the database created by
58
+ # +create_database+. This method is called twice, once before
59
+ # +create_database+ to ensure that no database exists and once
60
+ # at exit to clean as the agent process exits. This method
61
+ # must not fail if the database does not exist when it is called.
62
+ #
63
+ def drop_database
64
+ raise "Subclass must implement"
65
+ end
66
+
67
+ #
68
+ # Called before any agents are spawned to dump the schema that
69
+ # will be used for testing. When running distributed, this method
70
+ # is called on the local machine providing the tests to run.
71
+ #
72
+ # For distributed testing to work, the schema must be dumped in
73
+ # location accessible by all agent machines. The easiest way to
74
+ # accomplish this is to dump it to a location within the working copy.
75
+ #
76
+ def dump_schema
77
+ raise "Subclass must implement"
78
+ end
79
+
80
+
81
+ #
82
+ # Called once in each agent as it is starting to load the schema
83
+ # dumped from dump_schema. Subclasses should load the schema definition
84
+ # into the +agent_database+
85
+ #
86
+ def load_schema
87
+ raise "Subclass must implement"
88
+ end
89
+
90
+ #
91
+ # ActiveRecord configuration for the agent database. By default,
92
+ # the same as +master_database_config+, except that points to
93
+ # +agent_database+ instead of the database named in the master config.
94
+ #
95
+ def agent_database_config
96
+ master_database_config.merge(:database => agent_database)
97
+ end
98
+
99
+ #
100
+ # ActiveRecord configuration for the master database, based on
101
+ # RAILS_ENV. If not running Rails, you'll need to override this
102
+ # to provide the correct configuration.
103
+ #
104
+ def master_database_config
105
+ ActiveRecord::Base.configurations[RAILS_ENV].with_indifferent_access
106
+ end
107
+
108
+ #
109
+ # Unique name for database on machine that agent is running on.
110
+ #
111
+ def agent_database
112
+ "deep_test_agent_#{@agent.number}_pid_#{Process.pid}"
113
+ end
114
+ end
115
+ end
116
+ end