deep_test 1.1.4 → 1.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 (99) hide show
  1. data/CHANGELOG +15 -5
  2. data/README +149 -5
  3. data/Rakefile +131 -11
  4. data/bin/deep_test +15 -0
  5. data/lib/deep_test.rb +62 -4
  6. data/lib/deep_test/database/mysql_setup_listener.rb +109 -0
  7. data/lib/deep_test/database/setup_listener.rb +116 -0
  8. data/lib/deep_test/distributed/dispatch_controller.rb +53 -0
  9. data/lib/deep_test/distributed/drb_client_connection_info.rb +15 -0
  10. data/lib/deep_test/distributed/filename_resolver.rb +40 -0
  11. data/lib/deep_test/distributed/master_test_server.rb +52 -0
  12. data/lib/deep_test/distributed/multi_test_server_proxy.rb +44 -0
  13. data/lib/deep_test/distributed/null_work_unit.rb +12 -0
  14. data/lib/deep_test/distributed/remote_worker_client.rb +54 -0
  15. data/lib/deep_test/distributed/remote_worker_server.rb +82 -0
  16. data/lib/deep_test/distributed/rsync.rb +37 -0
  17. data/lib/deep_test/distributed/show_status.rhtml +41 -0
  18. data/lib/deep_test/distributed/test_server.rb +78 -0
  19. data/lib/deep_test/distributed/test_server_status.rb +9 -0
  20. data/lib/deep_test/distributed/test_server_workers.rb +24 -0
  21. data/lib/deep_test/distributed/throughput_runner.rb +42 -0
  22. data/lib/deep_test/distributed/throughput_statistics.rb +26 -0
  23. data/lib/deep_test/distributed/throughput_worker_client.rb +19 -0
  24. data/lib/deep_test/extensions/drb_extension.rb +34 -0
  25. data/lib/deep_test/extensions/object_extension.rb +8 -0
  26. data/lib/deep_test/listener_list.rb +17 -0
  27. data/lib/deep_test/local_workers.rb +55 -0
  28. data/lib/deep_test/logger.rb +10 -2
  29. data/lib/deep_test/marshallable_exception_wrapper.rb +44 -0
  30. data/lib/deep_test/metrics/gatherer.rb +67 -0
  31. data/lib/deep_test/metrics/queue_lock_wait_time_measurement.rb +133 -0
  32. data/lib/deep_test/null_worker_listener.rb +50 -0
  33. data/lib/deep_test/option.rb +60 -0
  34. data/lib/deep_test/options.rb +61 -24
  35. data/lib/deep_test/process_orchestrator.rb +31 -78
  36. data/lib/deep_test/rake_tasks.rb +3 -0
  37. data/lib/deep_test/result_reader.rb +36 -0
  38. data/lib/deep_test/rspec_detector.rb +15 -8
  39. data/lib/deep_test/server.rb +68 -4
  40. data/lib/deep_test/spec.rb +1 -0
  41. data/lib/deep_test/spec/extensions/example_group_methods.rb +37 -11
  42. data/lib/deep_test/spec/extensions/example_methods.rb +46 -0
  43. data/lib/deep_test/spec/extensions/options.rb +35 -3
  44. data/lib/deep_test/spec/runner.rb +19 -10
  45. data/lib/deep_test/spec/work_result.rb +12 -9
  46. data/lib/deep_test/spec/work_unit.rb +12 -7
  47. data/lib/deep_test/test.rb +1 -1
  48. data/lib/deep_test/test/extensions/error.rb +7 -7
  49. data/lib/deep_test/test/runner.rb +1 -6
  50. data/lib/deep_test/test/supervised_test_suite.rb +19 -16
  51. data/lib/deep_test/test/work_result.rb +34 -0
  52. data/lib/deep_test/test/work_unit.rb +6 -2
  53. data/lib/deep_test/test_task.rb +15 -35
  54. data/lib/deep_test/ui/console.rb +76 -0
  55. data/lib/deep_test/ui/null.rb +17 -0
  56. data/lib/deep_test/warlock.rb +92 -17
  57. data/lib/deep_test/worker.rb +38 -2
  58. data/script/internal/run_test_suite.rb +7 -0
  59. data/script/public/master_test_server.rb +24 -0
  60. data/script/public/test_server.rb +18 -0
  61. data/script/public/test_throughput.rb +29 -0
  62. data/test/deep_test/database/mysql_setup_listener_test.rb +14 -0
  63. data/test/deep_test/distributed/dispatch_controller_test.rb +209 -0
  64. data/test/deep_test/distributed/drb_client_connection_info_test.rb +42 -0
  65. data/test/deep_test/distributed/filename_resolver_test.rb +52 -0
  66. data/test/deep_test/distributed/master_test_server_test.rb +32 -0
  67. data/test/deep_test/distributed/multi_test_server_proxy_test.rb +96 -0
  68. data/test/deep_test/distributed/remote_worker_client_test.rb +180 -0
  69. data/test/deep_test/distributed/remote_worker_server_test.rb +99 -0
  70. data/test/deep_test/distributed/rsync_test.rb +67 -0
  71. data/test/deep_test/distributed/test_server_test.rb +94 -0
  72. data/test/deep_test/distributed/test_server_workers_test.rb +26 -0
  73. data/test/deep_test/distributed/throughput_runner_test.rb +68 -0
  74. data/test/deep_test/distributed/throughput_worker_client_test.rb +28 -0
  75. data/test/deep_test/listener_list_test.rb +20 -0
  76. data/test/deep_test/local_workers_test.rb +22 -0
  77. data/test/{logger_test.rb → deep_test/logger_test.rb} +2 -2
  78. data/test/deep_test/marshallable_exception_wrapper_test.rb +44 -0
  79. data/test/deep_test/metrics/gatherer_test.rb +66 -0
  80. data/test/deep_test/process_orchestrator_test.rb +11 -0
  81. data/test/deep_test/result_reader_test.rb +128 -0
  82. data/test/deep_test/server_test.rb +58 -0
  83. data/test/deep_test/test/extensions/error_test.rb +40 -0
  84. data/test/deep_test/test/supervised_test_suite_test.rb +19 -29
  85. data/test/deep_test/test/work_result_test.rb +81 -0
  86. data/test/deep_test/test/work_unit_test.rb +15 -4
  87. data/test/deep_test/test_task_test.rb +10 -0
  88. data/test/deep_test/ui/console_test.rb +9 -0
  89. data/test/deep_test/warlock_test.rb +17 -0
  90. data/test/deep_test/worker_test.rb +32 -0
  91. data/test/simple_test_blackboard.rb +2 -1
  92. data/test/test_helper.rb +1 -0
  93. metadata +117 -59
  94. data/lib/deep_test/rinda_blackboard.rb +0 -26
  95. data/lib/deep_test/test/extensions/test_result.rb +0 -17
  96. data/lib/deep_test/tuple_space_factory.rb +0 -12
  97. data/script/run_test_suite.rb +0 -5
  98. data/test/deep_test/rinda_blackboard_test.rb +0 -15
  99. data/test/deep_test/test/extensions/test_result_test.rb +0 -71
@@ -0,0 +1,109 @@
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 worker_database
30
+ grant_privileges connection
31
+ end
32
+ end
33
+
34
+ #
35
+ # Grants 'all' privilege on worker database to username and password
36
+ # specified by worker 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
+ sql = %{grant all on #{worker_database}.*
42
+ to %s@'localhost' identified by %s;} % [
43
+ connection.quote(worker_database_config[:username]),
44
+ connection.quote(worker_database_config[:password])
45
+ ]
46
+ connection.execute sql
47
+ end
48
+
49
+ #
50
+ # Drops database via ActiveRecord connection based on admin_configuration
51
+ #
52
+ def drop_database
53
+ admin_connection do |connection|
54
+ connection.drop_database worker_database
55
+ end
56
+ end
57
+
58
+ #
59
+ # Dumps schema from master database using mysqldump command
60
+ #
61
+ def dump_schema
62
+ config = command_line_config(master_database_config)
63
+ system "mysqldump -R #{config} > #{dump_file_name}"
64
+ raise "Error Dumping schema" unless $?.success?
65
+ end
66
+
67
+ #
68
+ # Loads dumpfile into worker database using mysql command
69
+ #
70
+ def load_schema
71
+ config = command_line_config(worker_database_config)
72
+ system "mysql #{config} < #{dump_file_name}"
73
+ raise "Error Loading schema" unless $?.success?
74
+ end
75
+
76
+ #
77
+ # Location to store dumpfile. The default assumes you are testing
78
+ # a Rails project. You should override this if you are not using Rails
79
+ # or would like the dump file to be something other than the default
80
+ #
81
+ def dump_file_name
82
+ "#{RAILS_ROOT}/db/deep_test_schema.sql"
83
+ end
84
+
85
+ def system(command) # :nodoc:
86
+ DeepTest.logger.info command
87
+ super command
88
+ end
89
+
90
+ def command_line_config(config) # :nodoc:
91
+ command = ['-u', config[:username]]
92
+ command += ["-p#{config[:password]}"] if config[:password]
93
+ command += ['-h', config[:host]] if config[:host]
94
+ command += ['-P', config[:port]] if config[:port]
95
+ command += ['-S', config[:socket]] if config[:socket]
96
+ command += [config[:database]]
97
+ command.join(' ')
98
+ end
99
+
100
+ def admin_connection # :nodoc:
101
+ conn = ActiveRecord::Base.mysql_connection(self.class.admin_configuration)
102
+ yield conn
103
+ ensure
104
+ conn.disconnect! if conn
105
+ end
106
+ end
107
+ end
108
+ end
109
+
@@ -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 worker. 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 < NullWorkerListener
10
+ DUMPED_SCHEMAS = [] unless defined?(DUMPED_SCHEMAS)
11
+
12
+ def before_sync # :nodoc:
13
+ dump_schema_once
14
+ end
15
+
16
+ def before_starting_workers # :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(worker) # :nodoc:
27
+ @worker = worker
28
+
29
+ at_exit do
30
+ DeepTest.logger.debug("dropping database #{worker_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 worker after creating database and before loading
42
+ # schema to initialize connections
43
+ #
44
+ def connect_to_database
45
+ ActiveRecord::Base.establish_connection(worker_database_config)
46
+ end
47
+
48
+ #
49
+ # Called in each worker to create the database named by
50
+ # +worker_database+.
51
+ #
52
+ def create_database
53
+ raise "Subclass must implement"
54
+ end
55
+
56
+ #
57
+ # Called in each worker 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 worker 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 workers 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 worker 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 worker as it is starting to load the schema
83
+ # dumped from dump_schema. Subclasses should load the schema definition
84
+ # into the +worker_database+
85
+ #
86
+ def load_schema
87
+ raise "Subclass must implement"
88
+ end
89
+
90
+ #
91
+ # ActiveRecord configuration for the worker database. By default,
92
+ # the same as +master_database_config+, except that points to
93
+ # +worker_database+ instead of the database named in the master config.
94
+ #
95
+ def worker_database_config
96
+ master_database_config.merge(:database => worker_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 worker is running on.
110
+ #
111
+ def worker_database
112
+ "deep_test_worker_#{@worker.number}_pid_#{Process.pid}"
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,53 @@
1
+ module DeepTest
2
+ module Distributed
3
+ class DispatchController
4
+ def initialize(options, receivers)
5
+ @options = options
6
+ @receivers = receivers
7
+ end
8
+
9
+ def dispatch(method_name, *args)
10
+ dispatch_with_options(method_name, {}, *args)
11
+ end
12
+
13
+ def dispatch_with_options(method_name, options, *args)
14
+ raise NoDispatchReceiversError if @receivers.empty?
15
+
16
+ @options.ui_instance.dispatch_starting(method_name)
17
+
18
+ threads = @receivers.map do |r|
19
+ Thread.new do
20
+ Thread.current[:receiver] = r
21
+ Timeout.timeout(@options.timeout_in_seconds) do
22
+ r.send method_name, *args
23
+ end
24
+ end
25
+ end
26
+
27
+ results = []
28
+ threads.each do |t|
29
+ begin
30
+ results << t.value
31
+ rescue Timeout::Error
32
+ @receivers.delete t[:receiver]
33
+ DeepTest.logger.error "Timeout dispatching #{method_name} to #{t[:receiver].__drburi}"
34
+ rescue DRb::DRbConnError
35
+ @receivers.delete t[:receiver]
36
+ unless options[:ignore_connection_error]
37
+ DeepTest.logger.error "Connection Refused dispatching #{method_name} to #{t[:receiver].__drburi}"
38
+ end
39
+ rescue Exception => e
40
+ @receivers.delete t[:receiver]
41
+ DeepTest.logger.error "Exception while dispatching #{method_name} to #{t[:receiver].__drburi} #{e.message}"
42
+ end
43
+ end
44
+
45
+ results
46
+ ensure
47
+ @options.ui_instance.dispatch_finished(method_name)
48
+ end
49
+ end
50
+
51
+ class NoDispatchReceiversError < StandardError; end
52
+ end
53
+ end
@@ -0,0 +1,15 @@
1
+ module DeepTest
2
+ module Distributed
3
+ class DRbClientConnectionInfo
4
+ attr_reader :address
5
+
6
+ def initialize
7
+ info = Thread.current['DRb']
8
+ raise "No DRb client found" unless info && info['client']
9
+ peeraddr = info['client'].peeraddr
10
+ DeepTest.logger.debug("DRbClientConnection info: #{peeraddr.inspect}")
11
+ @address = peeraddr[3]
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,40 @@
1
+ module DeepTest
2
+ module Distributed
3
+ class FilenameResolver
4
+ def initialize(base_path)
5
+ @base_path = base_path
6
+ end
7
+
8
+ def resolve(filename)
9
+ return resolve("/" + filename) unless filename[0] == ?/
10
+
11
+ return filename.sub(@cached_replaced_path, @base_path) if @cached_replaced_path
12
+
13
+ each_potential_filename(filename) do |potential_filename|
14
+ if File.exist?(potential_filename)
15
+ cache_resolution(filename, potential_filename)
16
+ return potential_filename
17
+ end
18
+ end
19
+
20
+ raise "Filename resolution failed. Cannot resolve #{filename} within #{@base_path}"
21
+ end
22
+
23
+ def cache_resolution(original_filename, resolved_filename)
24
+ @cached_replaced_path = original_filename.sub(
25
+ resolved_filename.sub(@base_path, ""), ""
26
+ )
27
+ end
28
+
29
+ def each_potential_filename(filename)
30
+ basename = File.basename(filename)
31
+ dirs = File.dirname(filename).split('/')
32
+
33
+ begin
34
+ dirs.shift
35
+ yield [@base_path, dirs, basename].flatten.join('/')
36
+ end until dirs.empty?
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,52 @@
1
+ module DeepTest
2
+ module Distributed
3
+ class MasterTestServer
4
+ include ERB::Util
5
+
6
+ STATUS_PORT = 4020 unless defined?(STATUS_PORT)
7
+
8
+ attr_reader :servers
9
+
10
+ def initialize(servers)
11
+ @servers = servers
12
+ end
13
+
14
+ def show_status(req, res)
15
+ template = File.read(File.dirname(__FILE__) + "/show_status.rhtml")
16
+ res.body = ERB.new(template).result(binding)
17
+ end
18
+
19
+ def test_server_statuses
20
+ @servers.map do |s|
21
+ status = begin
22
+ s.status
23
+ rescue Exception => e
24
+ e
25
+ end
26
+
27
+ [s.__drburi, status]
28
+ end
29
+ end
30
+
31
+ def self.start(uri, server_uris)
32
+ master = start_drb(uri, server_uris)
33
+ start_http(master)
34
+ DeepTest.logger.info "MasterTestServer listening at #{DRb.uri}"
35
+ DRb.thread.join
36
+ end
37
+
38
+ def self.start_drb(uri, server_uris)
39
+ servers = server_uris.map {|server_uri| DRbObject.new_with_uri server_uri}
40
+ master = DeepTest::Distributed::MasterTestServer.new(servers)
41
+ DRb.start_service(uri, master)
42
+ master
43
+ end
44
+
45
+ def self.start_http(master)
46
+ s = WEBrick::HTTPServer.new :Port => STATUS_PORT
47
+ s.mount_proc("/", &master.method(:show_status))
48
+ Thread.new {s.start}
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,44 @@
1
+ module DeepTest
2
+ module Distributed
3
+ class MultiTestServerProxy
4
+ def initialize(options, slaves)
5
+ DeepTest.logger.debug "MultiTestServerProxy#initialize #{slaves.length} slaves"
6
+ @slave_controller = DispatchController.new(options, slaves)
7
+ end
8
+
9
+ def spawn_worker_server(options)
10
+ DeepTest.logger.debug "dispatch spawn_worker_server for #{options.origin_hostname}"
11
+ WorkerServerProxy.new options,
12
+ @slave_controller.dispatch(:spawn_worker_server,
13
+ options)
14
+ end
15
+
16
+ def sync(options)
17
+ DeepTest.logger.debug "dispatch sync for #{options.origin_hostname}"
18
+ @slave_controller.dispatch(:sync, options)
19
+ end
20
+
21
+ class WorkerServerProxy
22
+ def initialize(options, slaves)
23
+ DeepTest.logger.debug "WorkerServerProxy#initialize #{slaves.inspect}"
24
+ @slave_controller = DispatchController.new(options, slaves)
25
+ end
26
+
27
+ def load_files(files)
28
+ DeepTest.logger.debug "dispatch load_files"
29
+ @slave_controller.dispatch(:load_files, files)
30
+ end
31
+
32
+ def start_all
33
+ DeepTest.logger.debug "dispatch start_all"
34
+ @slave_controller.dispatch(:start_all)
35
+ end
36
+
37
+ def stop_all
38
+ DeepTest.logger.debug "dispatch stop_all"
39
+ @slave_controller.dispatch_with_options(:stop_all, :ignore_connection_error => true)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,12 @@
1
+ module DeepTest
2
+ module Distributed
3
+ #
4
+ # Work Unit used to measure throughput of workers.
5
+ #
6
+ class NullWorkUnit
7
+ def run
8
+ :null_work_unit_result
9
+ end
10
+ end
11
+ end
12
+ end