jperkins-deep_test 1.2.2

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 (117) hide show
  1. data/CHANGELOG +47 -0
  2. data/README.rdoc +228 -0
  3. data/Rakefile +247 -0
  4. data/bin/deep_test +15 -0
  5. data/lib/deep_test.rb +91 -0
  6. data/lib/deep_test/database/mysql_setup_listener.rb +112 -0
  7. data/lib/deep_test/database/postgresql_setup_listener.rb +116 -0
  8. data/lib/deep_test/database/setup_listener.rb +125 -0
  9. data/lib/deep_test/deadlock_detector.rb +7 -0
  10. data/lib/deep_test/distributed/dispatch_controller.rb +53 -0
  11. data/lib/deep_test/distributed/drb_client_connection_info.rb +15 -0
  12. data/lib/deep_test/distributed/filename_resolver.rb +40 -0
  13. data/lib/deep_test/distributed/master_test_server.rb +52 -0
  14. data/lib/deep_test/distributed/multi_test_server_proxy.rb +44 -0
  15. data/lib/deep_test/distributed/null_work_unit.rb +12 -0
  16. data/lib/deep_test/distributed/remote_worker_client.rb +54 -0
  17. data/lib/deep_test/distributed/remote_worker_server.rb +82 -0
  18. data/lib/deep_test/distributed/rsync.rb +37 -0
  19. data/lib/deep_test/distributed/show_status.rhtml +41 -0
  20. data/lib/deep_test/distributed/test_server.rb +78 -0
  21. data/lib/deep_test/distributed/test_server_status.rb +9 -0
  22. data/lib/deep_test/distributed/test_server_workers.rb +24 -0
  23. data/lib/deep_test/distributed/throughput_runner.rb +42 -0
  24. data/lib/deep_test/distributed/throughput_statistics.rb +26 -0
  25. data/lib/deep_test/distributed/throughput_worker_client.rb +19 -0
  26. data/lib/deep_test/extensions/drb_extension.rb +34 -0
  27. data/lib/deep_test/extensions/object_extension.rb +40 -0
  28. data/lib/deep_test/listener_list.rb +17 -0
  29. data/lib/deep_test/local_workers.rb +55 -0
  30. data/lib/deep_test/logger.rb +17 -0
  31. data/lib/deep_test/marshallable_exception_wrapper.rb +44 -0
  32. data/lib/deep_test/metrics/gatherer.rb +67 -0
  33. data/lib/deep_test/metrics/queue_lock_wait_time_measurement.rb +133 -0
  34. data/lib/deep_test/null_worker_listener.rb +62 -0
  35. data/lib/deep_test/option.rb +60 -0
  36. data/lib/deep_test/options.rb +110 -0
  37. data/lib/deep_test/process_orchestrator.rb +49 -0
  38. data/lib/deep_test/rake_tasks.rb +11 -0
  39. data/lib/deep_test/result_reader.rb +36 -0
  40. data/lib/deep_test/rspec_detector.rb +21 -0
  41. data/lib/deep_test/server.rb +75 -0
  42. data/lib/deep_test/spec.rb +13 -0
  43. data/lib/deep_test/spec/extensions/example_group_methods.rb +64 -0
  44. data/lib/deep_test/spec/extensions/example_methods.rb +46 -0
  45. data/lib/deep_test/spec/extensions/options.rb +43 -0
  46. data/lib/deep_test/spec/extensions/reporter.rb +29 -0
  47. data/lib/deep_test/spec/extensions/spec_task.rb +20 -0
  48. data/lib/deep_test/spec/runner.rb +57 -0
  49. data/lib/deep_test/spec/work_result.rb +33 -0
  50. data/lib/deep_test/spec/work_unit.rb +59 -0
  51. data/lib/deep_test/test.rb +10 -0
  52. data/lib/deep_test/test/extensions/error.rb +14 -0
  53. data/lib/deep_test/test/runner.rb +24 -0
  54. data/lib/deep_test/test/supervised_test_suite.rb +49 -0
  55. data/lib/deep_test/test/work_result.rb +34 -0
  56. data/lib/deep_test/test/work_unit.rb +40 -0
  57. data/lib/deep_test/test_task.rb +47 -0
  58. data/lib/deep_test/ui/console.rb +76 -0
  59. data/lib/deep_test/ui/null.rb +17 -0
  60. data/lib/deep_test/warlock.rb +134 -0
  61. data/lib/deep_test/worker.rb +57 -0
  62. data/script/internal/run_test_suite.rb +7 -0
  63. data/script/public/master_test_server.rb +24 -0
  64. data/script/public/test_server.rb +18 -0
  65. data/script/public/test_throughput.rb +29 -0
  66. data/spec/deep_test/option_spec.rb +33 -0
  67. data/spec/deep_test/options_spec.rb +183 -0
  68. data/spec/deep_test/spec/extensions/example_group_methods_spec.rb +48 -0
  69. data/spec/deep_test/spec/extensions/example_methods_spec.rb +61 -0
  70. data/spec/deep_test/spec/extensions/options_spec.rb +23 -0
  71. data/spec/deep_test/spec/extensions/reporter_spec.rb +28 -0
  72. data/spec/deep_test/spec/extensions/spec_task_spec.rb +36 -0
  73. data/spec/deep_test/spec/runner_spec.rb +106 -0
  74. data/spec/deep_test/spec/work_result_spec.rb +14 -0
  75. data/spec/deep_test/spec/work_unit_spec.rb +78 -0
  76. data/spec/spec_helper.rb +59 -0
  77. data/spec/thread_worker.rb +25 -0
  78. data/test/deep_test/database/mysql_setup_listener_test.rb +14 -0
  79. data/test/deep_test/distributed/dispatch_controller_test.rb +209 -0
  80. data/test/deep_test/distributed/drb_client_connection_info_test.rb +42 -0
  81. data/test/deep_test/distributed/filename_resolver_test.rb +52 -0
  82. data/test/deep_test/distributed/master_test_server_test.rb +32 -0
  83. data/test/deep_test/distributed/multi_test_server_proxy_test.rb +96 -0
  84. data/test/deep_test/distributed/remote_worker_client_test.rb +180 -0
  85. data/test/deep_test/distributed/remote_worker_server_test.rb +99 -0
  86. data/test/deep_test/distributed/rsync_test.rb +67 -0
  87. data/test/deep_test/distributed/test_server_test.rb +94 -0
  88. data/test/deep_test/distributed/test_server_workers_test.rb +26 -0
  89. data/test/deep_test/distributed/throughput_runner_test.rb +68 -0
  90. data/test/deep_test/distributed/throughput_worker_client_test.rb +28 -0
  91. data/test/deep_test/extensions/object_extension_test.rb +37 -0
  92. data/test/deep_test/listener_list_test.rb +20 -0
  93. data/test/deep_test/local_workers_test.rb +22 -0
  94. data/test/deep_test/logger_test.rb +11 -0
  95. data/test/deep_test/marshallable_exception_wrapper_test.rb +44 -0
  96. data/test/deep_test/metrics/gatherer_test.rb +66 -0
  97. data/test/deep_test/process_orchestrator_test.rb +11 -0
  98. data/test/deep_test/result_reader_test.rb +128 -0
  99. data/test/deep_test/server_test.rb +58 -0
  100. data/test/deep_test/test/extensions/error_test.rb +40 -0
  101. data/test/deep_test/test/runner_test.rb +7 -0
  102. data/test/deep_test/test/supervised_test_suite_test.rb +79 -0
  103. data/test/deep_test/test/work_result_test.rb +81 -0
  104. data/test/deep_test/test/work_unit_test.rb +61 -0
  105. data/test/deep_test/test_task_test.rb +43 -0
  106. data/test/deep_test/ui/console_test.rb +9 -0
  107. data/test/deep_test/warlock_test.rb +38 -0
  108. data/test/deep_test/worker_test.rb +94 -0
  109. data/test/failing.rake +11 -0
  110. data/test/failing.rb +7 -0
  111. data/test/fake_deadlock_error.rb +12 -0
  112. data/test/simple_test_blackboard.rb +45 -0
  113. data/test/simple_test_blackboard_test.rb +33 -0
  114. data/test/test_factory.rb +74 -0
  115. data/test/test_helper.rb +15 -0
  116. data/test/test_task_test.rb +72 -0
  117. metadata +214 -0
@@ -0,0 +1,78 @@
1
+ module DeepTest
2
+ module Distributed
3
+ class TestServer
4
+ DEFAULT_CONFIG = {
5
+ :work_dir => "/tmp",
6
+ :uri => "drubyall://:4022",
7
+ :number_of_workers => 2
8
+ } unless defined?(DEFAULT_CONFIG)
9
+
10
+ def initialize(config)
11
+ @config = config
12
+ end
13
+
14
+ def spawn_worker_server(options)
15
+ DeepTest.logger.debug("mirror spawn_worker_server for #{options.origin_hostname}")
16
+ RemoteWorkerServer.start(URI.parse(@config[:uri]).host,
17
+ options.mirror_path(@config[:work_dir]),
18
+ TestServerWorkers.new(options, @config, DRbClientConnectionInfo.new))
19
+ end
20
+
21
+ def status
22
+ TestServerStatus.new(
23
+ DRb.uri,
24
+ @config[:number_of_workers],
25
+ RemoteWorkerServer.running_server_count
26
+ )
27
+ end
28
+
29
+ def sync(options)
30
+ DeepTest.logger.debug "mirror sync for #{options.origin_hostname}"
31
+ path = options.mirror_path(@config[:work_dir])
32
+ DeepTest.logger.debug "Syncing #{options.sync_options[:source]} to #{path}"
33
+ RSync.sync(DRbClientConnectionInfo.new, options, path)
34
+ end
35
+
36
+ def servers
37
+ [DRbObject.new_with_uri(DRb.uri)]
38
+ end
39
+
40
+ def self.start(config)
41
+ server = DeepTest::Distributed::TestServer.new(config)
42
+ DRb.start_service(config[:uri], server)
43
+ DeepTest.logger.info "TestServer listening at #{DRb.uri}"
44
+ DRb.thread.join
45
+ end
46
+
47
+ def self.parse_args(args)
48
+ options = DeepTest::Distributed::TestServer::DEFAULT_CONFIG.dup
49
+ OptionParser.new do |opts|
50
+ opts.banner = "Usage: deep_test test_server [options]"
51
+
52
+ opts.on("--work_dir PATH", "Absolute path to keep mirror working copies at") do |v|
53
+ options[:work_dir] = v
54
+ end
55
+
56
+ opts.on("--uri URI", "DRb URI to bind server to") do |v|
57
+ options[:uri] = v
58
+ end
59
+
60
+ opts.on("--number_of_workers NUM", "Number of workers to start when running tests") do |v|
61
+ options[:number_of_workers] = v.to_i
62
+ end
63
+
64
+ opts.on_tail("-h", "--help", "Show this message") do
65
+ puts opts
66
+ exit
67
+ end
68
+ end.parse(args)
69
+ options
70
+ end
71
+
72
+ def self.connect(options)
73
+ servers = DRbObject.new_with_uri(options.distributed_server).servers
74
+ MultiTestServerProxy.new(options, servers)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,9 @@
1
+ module DeepTest
2
+ module Distributed
3
+ TestServerStatus = Struct.new(
4
+ :binding_uri,
5
+ :number_of_workers,
6
+ :remote_worker_server_count
7
+ ) unless defined?(TestServerStatus)
8
+ end
9
+ end
@@ -0,0 +1,24 @@
1
+ module DeepTest
2
+ module Distributed
3
+ class TestServerWorkers < LocalWorkers
4
+ def initialize(options, test_server_config, connection_info)
5
+ super(options)
6
+ @test_server_config = test_server_config
7
+ @connection_info = connection_info
8
+ end
9
+
10
+ def number_of_workers
11
+ @test_server_config[:number_of_workers]
12
+ end
13
+
14
+ def server
15
+ Server.remote_reference(@connection_info.address, @options.server_port)
16
+ end
17
+
18
+ def start_all
19
+ super
20
+ @warlock.exit_when_none_running
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,42 @@
1
+ module DeepTest
2
+ module Distributed
3
+ class ThroughputRunner
4
+ def initialize(options, test_count, blackboard = nil, &block)
5
+ @options = options
6
+ @test_count = test_count
7
+ @blackboard = blackboard
8
+ @progress_block = block
9
+ end
10
+
11
+ def blackboard
12
+ @blackboard ||= @options.server
13
+ end
14
+
15
+ def statistics
16
+ ThroughputStatistics.new(@test_count, @start_time, @end_time)
17
+ end
18
+
19
+ def process_work_units
20
+ @start_time = Time.now
21
+
22
+ @test_count.times do
23
+ blackboard.write_work NullWorkUnit.new
24
+ end
25
+
26
+ results_read = 0
27
+ until results_read == @test_count
28
+ Thread.pass
29
+ result = blackboard.take_result
30
+ if result
31
+ results_read += 1
32
+ @progress_block.call(result) if @progress_block
33
+ end
34
+ end
35
+
36
+ @end_time = Time.now
37
+
38
+ true
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,26 @@
1
+ module DeepTest
2
+ module Distributed
3
+ class ThroughputStatistics
4
+ attr_reader :test_count, :start_time, :end_time
5
+
6
+ def initialize(test_count, start_time, end_time)
7
+ @test_count, @start_time, @end_time = test_count, start_time, end_time
8
+ end
9
+
10
+ def timespan_in_seconds
11
+ @end_time.to_f - @start_time.to_f
12
+ end
13
+
14
+ def tests_per_second
15
+ @test_count / timespan_in_seconds
16
+ end
17
+
18
+ def summary
19
+ <<-end_summary
20
+ #{test_count} tests run in #{timespan_in_seconds} seconds
21
+ (#{tests_per_second} tests / second)
22
+ end_summary
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,19 @@
1
+ module DeepTest
2
+ module Distributed
3
+ class ThroughputWorkerClient
4
+ def initialize(options, test_server)
5
+ @options = options
6
+ @test_server = test_server
7
+ end
8
+
9
+ def start_all
10
+ @worker_server = @test_server.spawn_worker_server(@options)
11
+ @worker_server.start_all
12
+ end
13
+
14
+ def stop_all
15
+ @worker_server.stop_all
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,34 @@
1
+ module DeepTest
2
+ class DRbBindAllTCPSocket < DRb::DRbTCPSocket
3
+ def self.parse_uri(uri)
4
+ if uri =~ /^drubyall:\/\/(.*?):(\d+)(\?(.*))?$/
5
+ host = $1
6
+ port = $2.to_i
7
+ option = $4
8
+ [host, port, option]
9
+ else
10
+ raise(DRb::DRbBadScheme, uri) unless uri =~ /^drubyall:/
11
+ raise(DRb::DRbBadURI, 'can\'t parse uri:' + uri)
12
+ end
13
+ end
14
+
15
+ # Open a server listening for connections at +uri+ using
16
+ # configuration +config+.
17
+ def self.open_server(uri, config)
18
+ uri = 'drubyall://:0' unless uri
19
+ host, port, opt = parse_uri(uri)
20
+
21
+ if host.size == 0
22
+ host = getservername
23
+ end
24
+
25
+ DeepTest.logger.debug("Listening on port #{port}, all addresses.")
26
+ soc = TCPServer.open('0.0.0.0', port)
27
+ port = soc.addr[1] if port == 0
28
+ uri = "druby://#{host}:#{port}"
29
+ self.new(uri, soc, config)
30
+ end
31
+ end
32
+ end
33
+
34
+ DRb::DRbProtocol.add_protocol DeepTest::DRbBindAllTCPSocket
@@ -0,0 +1,40 @@
1
+ require "stringio"
2
+ module DeepTest
3
+ module ObjectExtension
4
+ def capture_stdout
5
+ old_stdout, $stdout = $stdout, StringIO.new
6
+ yield
7
+ $stdout.string
8
+ ensure
9
+ $stdout = old_stdout if old_stdout
10
+ end
11
+
12
+ def capture_stderr
13
+ old_stderr, $stderr = $stderr, StringIO.new
14
+ yield
15
+ $stderr.string
16
+ ensure
17
+ $stderr = old_stderr if old_stderr
18
+ end
19
+
20
+ def retrying(description = nil, times = 5)
21
+ i = 0
22
+ loop do
23
+ begin
24
+ return yield
25
+ rescue => e
26
+ i += 1
27
+ print "#{description} received exception #{e}. "
28
+ if i < times
29
+ puts "Retrying..."
30
+ sleep 0.5
31
+ else
32
+ puts "Aborting."
33
+ raise e
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ Object.send :include, DeepTest::ObjectExtension
@@ -0,0 +1,17 @@
1
+ module DeepTest
2
+ class ListenerList
3
+ attr_reader :listeners
4
+
5
+ def initialize(listeners)
6
+ @listeners = listeners
7
+ end
8
+
9
+ NullWorkerListener.instance_methods(false).each do |event|
10
+ eval <<-end_src
11
+ def #{event}(*args)
12
+ @listeners.each {|l| l.#{event}(*args)}
13
+ end
14
+ end_src
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,55 @@
1
+ module DeepTest
2
+ class LocalWorkers
3
+ def initialize(options)
4
+ @options = options
5
+ @warlock = Warlock.new
6
+ end
7
+
8
+ def load_files(files)
9
+ files.each {|f| load f}
10
+ end
11
+
12
+ def server
13
+ @options.server
14
+ end
15
+
16
+ def start_all
17
+ each_worker do |worker_num|
18
+ start_worker(worker_num) do
19
+ reseed_random_numbers
20
+ reconnect_to_database
21
+ worker = DeepTest::Worker.new(worker_num,
22
+ server,
23
+ @options.new_listener_list)
24
+ worker.run
25
+ end
26
+ end
27
+ end
28
+
29
+ def stop_all
30
+ @warlock.stop_all
31
+ end
32
+
33
+ def number_of_workers
34
+ @options.number_of_workers
35
+ end
36
+
37
+ private
38
+
39
+ def reconnect_to_database
40
+ ActiveRecord::Base.connection.reconnect! if defined?(ActiveRecord::Base)
41
+ end
42
+
43
+ def start_worker(worker_num, &blk)
44
+ @warlock.start("worker #{worker_num}", &blk)
45
+ end
46
+
47
+ def reseed_random_numbers
48
+ srand
49
+ end
50
+
51
+ def each_worker
52
+ number_of_workers.to_i.times { |worker_num| yield worker_num }
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,17 @@
1
+ module DeepTest
2
+ class Logger < ::Logger
3
+ def initialize(*args)
4
+ super
5
+ self.formatter = proc { |severity, time, progname, msg| "[DeepTest] #{msg}\n" }
6
+ self.level = configured_log_level
7
+ end
8
+
9
+ def configured_log_level
10
+ if ENV['DEEP_TEST_LOG_LEVEL']
11
+ Logger.const_get(ENV['DEEP_TEST_LOG_LEVEL'])
12
+ else
13
+ Logger::INFO
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,44 @@
1
+ module DeepTest
2
+ class MarshallableExceptionWrapper
3
+ attr_reader :classname, :message, :backtrace
4
+
5
+ def initialize(exception)
6
+ @classname = exception.class.name
7
+ @message = exception.message
8
+ @backtrace = exception.backtrace
9
+ end
10
+
11
+ def ==(other)
12
+ classname == other.classname &&
13
+ message == other.message &&
14
+ backtrace == other.backtrace
15
+ end
16
+
17
+ def resolve
18
+ begin
19
+ klass = eval("::" + classname)
20
+ resolved_message = message
21
+ rescue => e
22
+ DeepTest.logger.debug("Unable to load exception class: #{classname}: #{e.message}")
23
+ DeepTest.logger.debug(e.backtrace.join("\n"))
24
+
25
+ klass = UnloadableException
26
+ resolved_message = "#{classname}: #{message}"
27
+ end
28
+
29
+ begin
30
+ resolved_exception = klass.new resolved_message
31
+ rescue => e
32
+ DeepTest.logger.debug("Unable to instantiation exception class: #{classname}: #{e.message}")
33
+ DeepTest.logger.debug(e.backtrace.join("\n"))
34
+
35
+ resolved_exception = UnloadableException.new resolved_message
36
+ end
37
+
38
+ resolved_exception.set_backtrace backtrace
39
+ resolved_exception
40
+ end
41
+ end
42
+
43
+ class UnloadableException < StandardError; end
44
+ end
@@ -0,0 +1,67 @@
1
+ module DeepTest
2
+ module Metrics
3
+ class Gatherer
4
+ def self.setup(options)
5
+ $metrics_gatherer = new(options)
6
+
7
+ at_exit do
8
+ $metrics_gatherer.write_file
9
+ end
10
+ end
11
+
12
+ def self.enabled?
13
+ return false unless $metrics_gatherer
14
+ $metrics_gatherer.enabled?
15
+ end
16
+
17
+ def self.section(title, &block)
18
+ $metrics_gatherer.section(title, &block)
19
+ end
20
+
21
+ def initialize(options)
22
+ @options = options
23
+ @sections = []
24
+ end
25
+
26
+ def enabled?
27
+ !@options.metrics_file.nil?
28
+ end
29
+
30
+ def section(title, &block)
31
+ @sections << Section.new(title, &block) if enabled?
32
+ end
33
+
34
+ def render
35
+ @sections.map {|s| s.render}.join("\n")
36
+ end
37
+
38
+ def write_file
39
+ return unless enabled?
40
+ File.open(@options.metrics_file, "w") do |io|
41
+ io << render
42
+ end
43
+ end
44
+
45
+ class Section
46
+ def initialize(title, &generate_measurements_block)
47
+ @title = title
48
+ @generate_measurements_block = generate_measurements_block
49
+ end
50
+
51
+ def measurement(name, value)
52
+ @measurements << [name, value]
53
+ end
54
+
55
+ def gather_measurements
56
+ @measurements = []
57
+ @generate_measurements_block.call(self)
58
+ end
59
+
60
+ def render
61
+ gather_measurements
62
+ "[#{@title}]\n" + @measurements.map {|name, value| "#{name}: #{value}"}.join("\n")
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end