jason-o-matic-deep_test 1.2.2.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.
Files changed (104) hide show
  1. data/CHANGELOG +47 -0
  2. data/README.rdoc +228 -0
  3. data/Rakefile +211 -0
  4. data/bin/deep_test +15 -0
  5. data/lib/deep_test.rb +90 -0
  6. data/lib/deep_test/database/mysql_setup_listener.rb +113 -0
  7. data/lib/deep_test/database/setup_listener.rb +116 -0
  8. data/lib/deep_test/deadlock_detector.rb +7 -0
  9. data/lib/deep_test/distributed/dispatch_controller.rb +53 -0
  10. data/lib/deep_test/distributed/drb_client_connection_info.rb +15 -0
  11. data/lib/deep_test/distributed/filename_resolver.rb +40 -0
  12. data/lib/deep_test/distributed/master_test_server.rb +52 -0
  13. data/lib/deep_test/distributed/multi_test_server_proxy.rb +44 -0
  14. data/lib/deep_test/distributed/null_work_unit.rb +12 -0
  15. data/lib/deep_test/distributed/remote_worker_client.rb +54 -0
  16. data/lib/deep_test/distributed/remote_worker_server.rb +111 -0
  17. data/lib/deep_test/distributed/rsync.rb +37 -0
  18. data/lib/deep_test/distributed/show_status.rhtml +41 -0
  19. data/lib/deep_test/distributed/test_server.rb +79 -0
  20. data/lib/deep_test/distributed/test_server_status.rb +9 -0
  21. data/lib/deep_test/distributed/test_server_workers.rb +24 -0
  22. data/lib/deep_test/distributed/throughput_runner.rb +42 -0
  23. data/lib/deep_test/distributed/throughput_statistics.rb +26 -0
  24. data/lib/deep_test/distributed/throughput_worker_client.rb +19 -0
  25. data/lib/deep_test/extensions/drb_extension.rb +34 -0
  26. data/lib/deep_test/extensions/object_extension.rb +40 -0
  27. data/lib/deep_test/listener_list.rb +17 -0
  28. data/lib/deep_test/local_workers.rb +55 -0
  29. data/lib/deep_test/logger.rb +17 -0
  30. data/lib/deep_test/marshallable_exception_wrapper.rb +44 -0
  31. data/lib/deep_test/metrics/gatherer.rb +67 -0
  32. data/lib/deep_test/metrics/queue_lock_wait_time_measurement.rb +133 -0
  33. data/lib/deep_test/null_worker_listener.rb +65 -0
  34. data/lib/deep_test/option.rb +70 -0
  35. data/lib/deep_test/options.rb +112 -0
  36. data/lib/deep_test/process_orchestrator.rb +49 -0
  37. data/lib/deep_test/rake_tasks.rb +11 -0
  38. data/lib/deep_test/result_reader.rb +36 -0
  39. data/lib/deep_test/rspec_detector.rb +24 -0
  40. data/lib/deep_test/server.rb +75 -0
  41. data/lib/deep_test/spec.rb +12 -0
  42. data/lib/deep_test/spec/extensions/example_group_methods.rb +64 -0
  43. data/lib/deep_test/spec/extensions/example_methods.rb +46 -0
  44. data/lib/deep_test/spec/extensions/options.rb +43 -0
  45. data/lib/deep_test/spec/extensions/reporter.rb +29 -0
  46. data/lib/deep_test/spec/extensions/spec_task.rb +20 -0
  47. data/lib/deep_test/spec/runner.rb +57 -0
  48. data/lib/deep_test/spec/work_result.rb +33 -0
  49. data/lib/deep_test/spec/work_unit.rb +59 -0
  50. data/lib/deep_test/test.rb +10 -0
  51. data/lib/deep_test/test/extensions/error.rb +14 -0
  52. data/lib/deep_test/test/runner.rb +24 -0
  53. data/lib/deep_test/test/supervised_test_suite.rb +49 -0
  54. data/lib/deep_test/test/work_result.rb +34 -0
  55. data/lib/deep_test/test/work_unit.rb +40 -0
  56. data/lib/deep_test/test_task.rb +51 -0
  57. data/lib/deep_test/ui/console.rb +76 -0
  58. data/lib/deep_test/ui/null.rb +17 -0
  59. data/lib/deep_test/warlock.rb +134 -0
  60. data/lib/deep_test/worker.rb +57 -0
  61. data/script/internal/run_test_suite.rb +7 -0
  62. data/script/public/master_test_server.rb +24 -0
  63. data/script/public/test_server.rb +18 -0
  64. data/script/public/test_throughput.rb +29 -0
  65. data/test/deep_test/database/mysql_setup_listener_test.rb +14 -0
  66. data/test/deep_test/distributed/dispatch_controller_test.rb +209 -0
  67. data/test/deep_test/distributed/drb_client_connection_info_test.rb +42 -0
  68. data/test/deep_test/distributed/filename_resolver_test.rb +52 -0
  69. data/test/deep_test/distributed/master_test_server_test.rb +32 -0
  70. data/test/deep_test/distributed/multi_test_server_proxy_test.rb +96 -0
  71. data/test/deep_test/distributed/remote_worker_client_test.rb +180 -0
  72. data/test/deep_test/distributed/remote_worker_server_test.rb +107 -0
  73. data/test/deep_test/distributed/rsync_test.rb +67 -0
  74. data/test/deep_test/distributed/test_server_test.rb +94 -0
  75. data/test/deep_test/distributed/test_server_workers_test.rb +26 -0
  76. data/test/deep_test/distributed/throughput_runner_test.rb +68 -0
  77. data/test/deep_test/distributed/throughput_worker_client_test.rb +28 -0
  78. data/test/deep_test/extensions/object_extension_test.rb +37 -0
  79. data/test/deep_test/listener_list_test.rb +20 -0
  80. data/test/deep_test/local_workers_test.rb +22 -0
  81. data/test/deep_test/logger_test.rb +11 -0
  82. data/test/deep_test/marshallable_exception_wrapper_test.rb +44 -0
  83. data/test/deep_test/metrics/gatherer_test.rb +66 -0
  84. data/test/deep_test/process_orchestrator_test.rb +11 -0
  85. data/test/deep_test/result_reader_test.rb +128 -0
  86. data/test/deep_test/server_test.rb +58 -0
  87. data/test/deep_test/test/extensions/error_test.rb +40 -0
  88. data/test/deep_test/test/runner_test.rb +7 -0
  89. data/test/deep_test/test/supervised_test_suite_test.rb +79 -0
  90. data/test/deep_test/test/work_result_test.rb +81 -0
  91. data/test/deep_test/test/work_unit_test.rb +61 -0
  92. data/test/deep_test/test_task_test.rb +43 -0
  93. data/test/deep_test/ui/console_test.rb +9 -0
  94. data/test/deep_test/warlock_test.rb +38 -0
  95. data/test/deep_test/worker_test.rb +94 -0
  96. data/test/failing.rake +11 -0
  97. data/test/failing.rb +7 -0
  98. data/test/fake_deadlock_error.rb +12 -0
  99. data/test/simple_test_blackboard.rb +45 -0
  100. data/test/simple_test_blackboard_test.rb +33 -0
  101. data/test/test_factory.rb +74 -0
  102. data/test/test_helper.rb +15 -0
  103. data/test/test_task_test.rb +72 -0
  104. metadata +160 -0
data/bin/deep_test ADDED
@@ -0,0 +1,15 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ script_dir = File.dirname(__FILE__) + '/../script/public'
4
+ script_file = script_dir + "/#{ARGV.shift}.rb"
5
+
6
+ unless File.exist?(script_file)
7
+ puts 'Usage: deep_test <script> [arguments]'
8
+ puts ' Available Scripts: '
9
+ Dir.glob(script_dir + '/*.rb').each do |name|
10
+ puts " #{File.basename(name, '.rb')}"
11
+ end
12
+ exit(1)
13
+ end
14
+
15
+ load script_file
data/lib/deep_test.rb ADDED
@@ -0,0 +1,90 @@
1
+ module DeepTest
2
+ class << self
3
+ def logger
4
+ @logger ||= DeepTest::Logger.new($stdout)
5
+ end
6
+ end
7
+
8
+ # Fork in a separate thread. If we fork from a DRb thread
9
+ # (a thread handling a method call invoked over drb),
10
+ # DRb still thinks the current object is the DRb Front Object,
11
+ # and reports its uri as the same as in the parent process, even
12
+ # if you restart DRb service.
13
+ #
14
+ def self.drb_safe_fork(&block)
15
+ Thread.new {Process.fork(&block)}.value
16
+ end
17
+
18
+ def self.init(options)
19
+ return if @initialized
20
+ @initialized = true
21
+ Metrics::Gatherer.setup(options)
22
+ end
23
+
24
+ class WorkUnitNeverReceivedError < StandardError
25
+ def initialize
26
+ super "DeepTest result never received. Maybe an error was printed above?"
27
+ end
28
+
29
+ def backtrace
30
+ []
31
+ end
32
+ end
33
+ end
34
+
35
+ require "logger"
36
+ require "drb"
37
+ require "timeout"
38
+ require "thread"
39
+ require "socket"
40
+ require "webrick"
41
+ require "timeout"
42
+
43
+ require File.dirname(__FILE__) + "/deep_test/extensions/object_extension"
44
+ require File.dirname(__FILE__) + "/deep_test/extensions/drb_extension"
45
+
46
+ require File.dirname(__FILE__) + "/deep_test/deadlock_detector"
47
+ require File.dirname(__FILE__) + "/deep_test/local_workers"
48
+ require File.dirname(__FILE__) + "/deep_test/logger"
49
+
50
+ require File.dirname(__FILE__) + "/deep_test/marshallable_exception_wrapper"
51
+ require File.dirname(__FILE__) + "/deep_test/null_worker_listener"
52
+ require File.dirname(__FILE__) + "/deep_test/listener_list"
53
+ require File.dirname(__FILE__) + "/deep_test/option"
54
+ require File.dirname(__FILE__) + "/deep_test/options"
55
+ require File.dirname(__FILE__) + "/deep_test/process_orchestrator"
56
+ require File.dirname(__FILE__) + "/deep_test/result_reader"
57
+ require File.dirname(__FILE__) + "/deep_test/rspec_detector"
58
+ require File.dirname(__FILE__) + "/deep_test/server"
59
+ require File.dirname(__FILE__) + "/deep_test/test_task"
60
+ require File.dirname(__FILE__) + "/deep_test/worker"
61
+ require File.dirname(__FILE__) + "/deep_test/warlock"
62
+
63
+ require File.dirname(__FILE__) + "/deep_test/database/setup_listener"
64
+ require File.dirname(__FILE__) + "/deep_test/database/mysql_setup_listener"
65
+
66
+ require File.dirname(__FILE__) + "/deep_test/distributed/dispatch_controller"
67
+ require File.dirname(__FILE__) + "/deep_test/distributed/drb_client_connection_info"
68
+ require File.dirname(__FILE__) + "/deep_test/distributed/filename_resolver"
69
+ require File.dirname(__FILE__) + "/deep_test/distributed/master_test_server"
70
+ require File.dirname(__FILE__) + "/deep_test/distributed/test_server"
71
+ require File.dirname(__FILE__) + "/deep_test/distributed/test_server_status"
72
+ require File.dirname(__FILE__) + "/deep_test/distributed/test_server_workers"
73
+ require File.dirname(__FILE__) + "/deep_test/distributed/multi_test_server_proxy"
74
+ require File.dirname(__FILE__) + "/deep_test/distributed/null_work_unit"
75
+ require File.dirname(__FILE__) + "/deep_test/distributed/remote_worker_client"
76
+ require File.dirname(__FILE__) + "/deep_test/distributed/remote_worker_server"
77
+ require File.dirname(__FILE__) + "/deep_test/distributed/rsync"
78
+ require File.dirname(__FILE__) + "/deep_test/distributed/throughput_runner"
79
+ require File.dirname(__FILE__) + "/deep_test/distributed/throughput_statistics"
80
+ require File.dirname(__FILE__) + "/deep_test/distributed/throughput_worker_client"
81
+
82
+ require File.dirname(__FILE__) + "/deep_test/metrics/gatherer"
83
+
84
+ DeepTest::RSpecDetector.if_rspec_available do
85
+ require File.dirname(__FILE__) + "/deep_test/spec"
86
+ end
87
+ require File.dirname(__FILE__) + "/deep_test/test"
88
+
89
+ require File.dirname(__FILE__) + "/deep_test/ui/console"
90
+ require File.dirname(__FILE__) + "/deep_test/ui/null"
@@ -0,0 +1,113 @@
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
+ identified_by = if worker_database_config["password"]
42
+ %{identified by %s} % connection.quote(worker_database_config["password"])
43
+ else
44
+ ""
45
+ end
46
+ sql = %{grant all on #{worker_database}.* to %s@'localhost' #{identified_by} ; } %
47
+ connection.quote(worker_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 worker_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 worker database using mysql command
72
+ #
73
+ def load_schema
74
+ config = command_line_config(worker_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
+ ActiveRecord::Base.establish_connection(self.class.admin_configuration)
105
+ conn = ActiveRecord::Base.connection
106
+ yield conn
107
+ ensure
108
+ conn.disconnect! if conn
109
+ end
110
+ end
111
+ end
112
+ end
113
+
@@ -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,7 @@
1
+ module DeepTest
2
+ class DeadlockDetector
3
+ def self.due_to_deadlock?(error)
4
+ error && !error.message.to_s.match(/Deadlock found when trying to get lock/).nil?
5
+ end
6
+ end
7
+ 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