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,10 @@
1
+ require "test/unit/testresult"
2
+ require "test/unit/error"
3
+ require 'test/unit/failure'
4
+ require 'test/unit/autorunner'
5
+
6
+ require File.dirname(__FILE__) + "/test/extensions/error"
7
+ require File.dirname(__FILE__) + "/test/runner"
8
+ require File.dirname(__FILE__) + "/test/supervised_test_suite"
9
+ require File.dirname(__FILE__) + "/test/work_unit"
10
+ require File.dirname(__FILE__) + "/test/work_result"
@@ -0,0 +1,14 @@
1
+ module Test
2
+ module Unit
3
+ class Error
4
+ def resolve_marshallable_exception
5
+ @exception = @exception.resolve if @exception.kind_of?(DeepTest::MarshallableExceptionWrapper)
6
+ end
7
+
8
+ def make_exception_marshallable
9
+ return if @exception.kind_of?(DeepTest::MarshallableExceptionWrapper)
10
+ @exception = DeepTest::MarshallableExceptionWrapper.new(@exception)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,24 @@
1
+ module DeepTest
2
+ module Test
3
+ class Runner
4
+ unless defined?(NO_FILTERS)
5
+ NO_FILTERS = Object.new.instance_eval do
6
+ def filters; []; end;
7
+ self
8
+ end
9
+ end
10
+
11
+ def initialize(options)
12
+ @options = options
13
+ end
14
+
15
+ def process_work_units
16
+ suite = ::Test::Unit::AutoRunner::COLLECTORS[:objectspace].call NO_FILTERS
17
+ supervised_suite = DeepTest::Test::SupervisedTestSuite.new(suite, @options.server)
18
+ require 'test/unit/ui/console/testrunner'
19
+ result = ::Test::Unit::UI::Console::TestRunner.run(supervised_suite, ::Test::Unit::UI::NORMAL)
20
+ result.passed?
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,49 @@
1
+ module DeepTest
2
+ module Test
3
+ class SupervisedTestSuite
4
+ def initialize(suite, blackboard)
5
+ @suite = suite
6
+ @blackboard = blackboard
7
+ end
8
+
9
+ def run(result, &progress_block)
10
+ yield ::Test::Unit::TestSuite::STARTED, @suite.name
11
+ tests_by_name = {}
12
+ add_tests @suite, tests_by_name
13
+ read_results result, tests_by_name, &progress_block
14
+ yield ::Test::Unit::TestSuite::FINISHED, @suite.name
15
+ end
16
+
17
+ def size
18
+ @suite.size
19
+ end
20
+
21
+ def add_tests(test_suite, tests_by_name)
22
+ if test_suite.respond_to? :tests
23
+ test_suite.tests.each do |test|
24
+ add_tests(test, tests_by_name)
25
+ end
26
+ else
27
+ tests_by_name[test_suite.name] = test_suite
28
+ @blackboard.write_work Test::WorkUnit.new(test_suite)
29
+ end
30
+ end
31
+
32
+ def read_results(result, tests_by_name)
33
+ DeepTest.logger.debug("SupervisedTestSuite: going to read #{tests_by_name.size} results")
34
+
35
+ missing_tests =
36
+ ResultReader.new(@blackboard).read(tests_by_name) do |test, remote_result|
37
+ remote_result.add_to result
38
+ yield ::Test::Unit::TestCase::FINISHED, test.name if block_given?
39
+ end
40
+
41
+ missing_tests.each do |name, test_case|
42
+ result.add_error ::Test::Unit::Error.new(name, WorkUnitNeverReceivedError.new)
43
+ end
44
+ ensure
45
+ DeepTest.logger.debug("SupervisedTestSuite: exiting with #{missing_tests.size} results left")
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,34 @@
1
+ module DeepTest
2
+ module Test
3
+ class WorkResult < ::Test::Unit::TestResult
4
+ attr_reader :identifier
5
+ attr_accessor :output
6
+
7
+ def initialize(identifier)
8
+ super()
9
+ @identifier = identifier
10
+ end
11
+
12
+ def add_to(result)
13
+ @failures.each {|e| result.add_failure(e)}
14
+
15
+ @errors.each do |e|
16
+ e.resolve_marshallable_exception
17
+ result.add_error(e)
18
+ end
19
+
20
+ assertion_count.times {result.add_assertion}
21
+ run_count.times {result.add_run}
22
+ end
23
+
24
+ def add_error(error)
25
+ error.make_exception_marshallable
26
+ super(error)
27
+ end
28
+
29
+ def failed_due_to_deadlock?
30
+ @errors.any? && DeepTest::DeadlockDetector.due_to_deadlock?(@errors.last)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,40 @@
1
+ module DeepTest
2
+ module Test
3
+ class WorkUnit
4
+ def initialize(test_case)
5
+ @test_case = test_case
6
+ end
7
+
8
+ def run
9
+ result = run_without_deadlock_protection
10
+ result = run_without_deadlock_protection if result.failed_due_to_deadlock?
11
+ if result.failed_due_to_deadlock?
12
+ result = WorkResult.new(@test_case.name)
13
+ result.add_run
14
+ result.output = "-deadlock-"
15
+ end
16
+ result
17
+ end
18
+
19
+ def ==(other)
20
+ return false unless other.class == self.class
21
+ @test_case == other.instance_variable_get(:@test_case)
22
+ end
23
+
24
+ def to_s
25
+ @test_case.to_s
26
+ end
27
+
28
+ protected
29
+
30
+ def run_without_deadlock_protection
31
+ result = WorkResult.new(@test_case.name)
32
+ output = capture_stdout do
33
+ @test_case.run(result) {|channel,event|}
34
+ end
35
+ result.output = output
36
+ result
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,47 @@
1
+ module DeepTest
2
+ class TestTask
3
+ attr_accessor :libs, :requires
4
+
5
+ def initialize(name = :deep_test)
6
+ @requires = []
7
+ @name = name
8
+ @libs = ["lib"]
9
+ @options = Options.new({})
10
+ self.pattern = "test/**/*_test.rb"
11
+ yield self if block_given?
12
+ define
13
+ end
14
+
15
+ def define
16
+ require 'rake'
17
+ desc "Run '#{@name}' suite using DeepTest"
18
+ task @name do
19
+ lib_options = @libs.any? ? "-I" + @libs.join(File::PATH_SEPARATOR) : ""
20
+ require_options = requires.map {|f| "-r#{f}"}.join(" ")
21
+ ruby "#{lib_options} #{require_options} #{runner} '#{@options.to_command_line}'"
22
+ end
23
+ end
24
+
25
+ Options::VALID_OPTIONS.each do |option|
26
+ eval <<-end_src
27
+ def #{option.name}
28
+ @options.#{option.name}
29
+ end
30
+
31
+ def #{option.name}=(value)
32
+ @options.#{option.name} = value
33
+ end
34
+ end_src
35
+ end
36
+
37
+ def pattern=(pattern)
38
+ @options.pattern = Dir.pwd + "/" + pattern
39
+ end
40
+
41
+ private
42
+
43
+ def runner
44
+ File.expand_path(File.dirname(__FILE__) + "/../../script/internal/run_test_suite.rb")
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,76 @@
1
+ module DeepTest
2
+ module UI
3
+ class Console
4
+ def initialize(options)
5
+ end
6
+
7
+ METHOD_DESCRIPTIONS = {
8
+ :sync => "Synchronizing working copies on worker machines",
9
+ :spawn_worker_server => "Spawning test environment processes",
10
+ :load_files => "Loading test files for workers",
11
+ :start_all => "Starting workers",
12
+ :stop_all => "Stopping workers"
13
+ } unless defined?(METHOD_DESCRIPTIONS)
14
+
15
+ def distributed_failover_to_local(method, exception)
16
+ width = 70
17
+ puts " Distributed DeepTest Failure ".center(width, '*')
18
+ puts "* Failed during #{method}".ljust(width - 1) + "*"
19
+ puts "* #{exception.message}".ljust(width - 1) + "*"
20
+ puts "* Failing over to local run".ljust(width - 1) + "*"
21
+ puts "*" * width
22
+ end
23
+
24
+ def dispatch_starting(method_name)
25
+ @spinner.stop if @spinner
26
+ @spinner = Spinner.new(label(method_name))
27
+ @spinner.start
28
+ end
29
+
30
+ def label(method_name)
31
+ METHOD_DESCRIPTIONS[method_name.to_sym] || method_name.to_s
32
+ end
33
+
34
+ def dispatch_finished(method_name)
35
+ @spinner.stop if @spinner
36
+ @spinner = nil
37
+ end
38
+
39
+ class Spinner
40
+ FRAMES = ['|', '/', '-', '\\'] unless defined?(FRAMES)
41
+ BACKSPACE = "\x08" unless defined?(BACKSPACE)
42
+ SECONDS_PER_FRAME = 0.5 / 4 unless defined?(SECONDS_PER_FRAME)
43
+
44
+ def initialize(label)
45
+ @label = label
46
+ end
47
+
48
+ def start
49
+ @start_time = Time.now
50
+ show "#{@label}: "
51
+ @thread = Thread.new do
52
+ index = 0
53
+ loop do
54
+ show FRAMES[index]
55
+ sleep SECONDS_PER_FRAME
56
+ show BACKSPACE
57
+ index = (index + 1) % FRAMES.length
58
+ end
59
+ end
60
+ end
61
+
62
+ def stop
63
+ @stop_time = Time.now
64
+ @thread.kill if @thread
65
+ show BACKSPACE
66
+ show("finished in %.2f seconds\n" % (@stop_time.to_f - @start_time.to_f))
67
+ end
68
+
69
+ def show(string)
70
+ $stdout.print string
71
+ $stdout.flush
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -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,134 @@
1
+ module DeepTest
2
+ class Warlock
3
+ def initialize
4
+ @demons_semaphore = Mutex.new
5
+ @demons = []
6
+ @reapers = []
7
+ end
8
+
9
+ def start(name, &block)
10
+ # Not synchronizing for the fork seems to cause
11
+ # random errors (Bus Error, Segfault, and GC non-object)
12
+ # in RemoteWorkerServer processes.
13
+ #
14
+ begin
15
+ pid = nil
16
+ @demons_semaphore.synchronize do
17
+ pid = DeepTest.drb_safe_fork do
18
+ # Fork leaves the semaphore locked and we'll never make it
19
+ # to end of synchronize block.
20
+ #
21
+ # The Ruby 1.8.6 C mutex implementation automatically treats
22
+ # a mutex locked by a dead thread as unlocked and will raise
23
+ # an error if we try to unlock it from this thread.
24
+ #
25
+ @demons_semaphore.unlock if @demons_semaphore.locked?
26
+
27
+ begin
28
+ yield
29
+ rescue Exception => e
30
+ DeepTest.logger.debug "Exception in #{name} (#{Process.pid}): #{e.message}"
31
+ raise
32
+ end
33
+
34
+ exit
35
+ end
36
+
37
+ raise "fatal: fork returned nil" if pid.nil?
38
+ add_demon name, pid
39
+ end
40
+
41
+ launch_reaper_thread name, pid
42
+
43
+ rescue => e
44
+ puts "exception starting #{name}: #{e}"
45
+ puts "\t" + e.backtrace.join("\n\t")
46
+ end
47
+ end
48
+
49
+ def demon_count
50
+ @demons_semaphore.synchronize do
51
+ @demons.size
52
+ end
53
+ end
54
+
55
+ def stop_all
56
+ DeepTest.logger.debug("stopping all demons")
57
+ receivers = @demons_semaphore.synchronize do
58
+ @demons.reverse
59
+ end
60
+
61
+ receivers.reverse.each do |demon|
62
+ name, pid = demon
63
+ if running?(pid)
64
+ DeepTest.logger.debug("Sending SIGTERM to #{name}, #{pid}")
65
+ Process.kill("TERM", pid)
66
+ end
67
+ end
68
+ DeepTest.logger.debug("Warlock: Stopped all receivers")
69
+
70
+ DeepTest.logger.debug("waiting for reapers")
71
+ @reapers.each {|r| r.join}
72
+
73
+ DeepTest.logger.debug("Warlock: done reaping processes")
74
+ end
75
+
76
+ def exit_when_none_running
77
+ Thread.new do
78
+ loop do
79
+ Thread.pass
80
+ exit(0) unless any_running?
81
+ sleep(0.01)
82
+ end
83
+ end
84
+ end
85
+
86
+ def any_running?
87
+ @demons_semaphore.synchronize do
88
+ @demons.any? {|name, pid| running?(pid)}
89
+ end
90
+ end
91
+
92
+ #stolen from daemons
93
+ def running?(pid)
94
+ # Check if process is in existence
95
+ # The simplest way to do this is to send signal '0'
96
+ # (which is a single system call) that doesn't actually
97
+ # send a signal
98
+ begin
99
+ Process.kill(0, pid)
100
+ return true
101
+ rescue Errno::ESRCH
102
+ return false
103
+ rescue ::Exception # for example on EPERM (process exists but does not belong to us)
104
+ return true
105
+ #rescue Errno::EPERM
106
+ # return false
107
+ end
108
+ end
109
+
110
+ protected
111
+
112
+ def add_demon(name, pid)
113
+ DeepTest.logger.debug "Started: #{name} (#{pid})"
114
+ @demons << [name, pid]
115
+ end
116
+
117
+ def remove_demon(name, pid)
118
+ @demons.delete [name, pid]
119
+ DeepTest.logger.debug "Stopped: #{name} (#{pid})"
120
+ end
121
+
122
+
123
+ def launch_reaper_thread(name, pid)
124
+ @reapers << Thread.new do
125
+ Process.detach(pid).join
126
+ DeepTest.logger.debug("#{name} (#{pid}) reaped")
127
+ @demons_semaphore.synchronize do
128
+ DeepTest.logger.debug("Warlock Reaper: removing #{name} (#{pid}) from demon list")
129
+ remove_demon name, pid
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end