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,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
@@ -1,40 +1,91 @@
1
1
  module DeepTest
2
2
  class Warlock
3
3
  def initialize
4
+ @demons_semaphore = Mutex.new
4
5
  @demons = []
6
+ @reapers = []
5
7
  end
6
-
8
+
7
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
+ #
8
14
  begin
9
- pid = Process.fork do
10
- Signal.trap("HUP") { exit 0 }
11
- yield
12
- exit
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
13
39
  end
14
- raise "fatal: fork returned nil" if pid.nil?
15
- @demons << [name, pid]
16
- DeepTest.logger.debug "Started #{name} (#{pid})"
40
+
41
+ launch_reaper_thread name, pid
42
+
17
43
  rescue => e
18
44
  puts "exception starting #{name}: #{e}"
19
45
  puts "\t" + e.backtrace.join("\n\t")
20
46
  end
21
47
  end
22
48
 
49
+ def demon_count
50
+ @demons_semaphore.synchronize do
51
+ @demons.size
52
+ end
53
+ end
54
+
23
55
  def stop_all
24
- @demons.reverse.each do |demon|
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|
25
62
  name, pid = demon
26
63
  if running?(pid)
27
- Process.kill("HUP", pid)
64
+ DeepTest.logger.debug("Sending SIGTERM to #{name}, #{pid}")
65
+ Process.kill("TERM", pid)
28
66
  end
29
67
  end
30
- @demons.each do |demon|
31
- name, pid = demon
32
- begin
33
- Process.wait(pid)
34
- rescue Errno::ECHILD => e
35
- puts e
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)
36
82
  end
37
- DeepTest.logger.debug "Stopped #{name} (#{pid})"
83
+ end
84
+ end
85
+
86
+ def any_running?
87
+ @demons_semaphore.synchronize do
88
+ @demons.any? {|name, pid| running?(pid)}
38
89
  end
39
90
  end
40
91
 
@@ -55,5 +106,29 @@ module DeepTest
55
106
  # return false
56
107
  end
57
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
58
133
  end
59
134
  end
@@ -10,11 +10,47 @@ module DeepTest
10
10
 
11
11
  def run
12
12
  @listener.starting(self)
13
- while work_unit = @blackboard.take_work
13
+ while work_unit = next_work_unit
14
14
  @listener.starting_work(self, work_unit)
15
- result = work_unit.run
15
+
16
+ result = begin
17
+ work_unit.run
18
+ rescue Exception => error
19
+ Error.new(work_unit, error)
20
+ end
21
+
16
22
  @listener.finished_work(self, work_unit, result)
17
23
  @blackboard.write_result result
24
+ if ENV['DEEP_TEST_SHOW_WORKER_DOTS'] == 'yes'
25
+ $stdout.print '.'
26
+ $stdout.flush
27
+ end
28
+ end
29
+ rescue Server::NoWorkUnitsRemainingError
30
+ DeepTest.logger.debug("Worker #{number}: no more work to do")
31
+ end
32
+
33
+ def next_work_unit
34
+ @blackboard.take_work
35
+ rescue Server::NoWorkUnitsAvailableError
36
+ sleep 0.02
37
+ retry
38
+ end
39
+
40
+ class Error
41
+ attr_accessor :work_unit, :error
42
+
43
+ def initialize(work_unit, error)
44
+ @work_unit, @error = work_unit, error
45
+ end
46
+
47
+ def ==(other)
48
+ work_unit == other.work_unit &&
49
+ error == other.error
50
+ end
51
+
52
+ def to_s
53
+ "#{@work_unit}: #{@error}\n" + (@error.backtrace || []).join("\n")
18
54
  end
19
55
  end
20
56
  end
@@ -0,0 +1,7 @@
1
+ require File.dirname(__FILE__) + "/../../lib/deep_test"
2
+ options = DeepTest::Options.from_command_line(ARGV[0])
3
+ DeepTest.init(options)
4
+ runner = DeepTest::Test::Runner.new(options)
5
+ workers = options.new_workers
6
+ workers.load_files Dir.glob(options.pattern)
7
+ DeepTest::ProcessOrchestrator.run(options, workers, runner)
@@ -0,0 +1,24 @@
1
+ require File.dirname(__FILE__) + "/../../lib/deep_test"
2
+ require 'drb/drb'
3
+ require 'optparse'
4
+
5
+ uri = "drubyall://:4021"
6
+ slave_uris = OptionParser.new do |opts|
7
+ opts.banner = "Usage: deep_test master_test_server [options] <test_server_uris>"
8
+
9
+ opts.on("--uri URI", "DRb URI to bind server to") do |v|
10
+ uri = v
11
+ end
12
+
13
+ opts.on_tail("-h", "--help", "Show this message") do
14
+ puts opts
15
+ exit
16
+ end
17
+ end.parse(ARGV)
18
+
19
+ begin
20
+ DeepTest::Distributed::MasterTestServer.start(uri, slave_uris)
21
+ rescue Interrupt
22
+ DeepTest.logger.info "Exiting due to Interrupt"
23
+ end
24
+
@@ -0,0 +1,18 @@
1
+ require File.dirname(__FILE__) + "/../../lib/deep_test"
2
+ require 'drb/drb'
3
+ require 'optparse'
4
+
5
+ config = DeepTest::Distributed::TestServer.parse_args(ARGV)
6
+
7
+ #
8
+ # Clear args so they won't be processed in any forked processes.
9
+ # - When specs are loaded by the RemoteWorkerServer, RSpec
10
+ # attempts to parse ARGV
11
+ #
12
+ ARGV.clear
13
+
14
+ begin
15
+ DeepTest::Distributed::TestServer.start(config)
16
+ rescue Interrupt
17
+ DeepTest.logger.info "Exiting due to Interrupt"
18
+ end
@@ -0,0 +1,29 @@
1
+ require File.dirname(__FILE__) + '/../../lib/deep_test'
2
+
3
+ unless ARGV.length == 2
4
+ puts "Usage: deep_test test_throughput <server uri> <test_count>"
5
+ exit(1)
6
+ end
7
+
8
+ uri = ARGV[0]
9
+ test_count = ARGV[1].to_i
10
+
11
+ options = DeepTest::Options.new(:distributed_server => uri,
12
+ :sync_options => {:source => ""})
13
+ server = DeepTest::Distributed::TestServer.connect options
14
+ workers = DeepTest::Distributed::ThroughputWorkerClient.new(options, server)
15
+ runner = DeepTest::Distributed::ThroughputRunner.new(options, test_count) do |result|
16
+ $stdout.print "."
17
+ $stdout.flush
18
+ end
19
+
20
+ start_time = Time.now
21
+ DeepTest::ProcessOrchestrator.new(options, workers, runner).run(false)
22
+ end_time = Time.now
23
+
24
+ puts
25
+ puts runner.statistics.summary
26
+
27
+ run_time = end_time.to_f - start_time.to_f
28
+ puts "Total Run Time: #{run_time} seconds"
29
+ puts "Run Time Not Spent On Tests: #{run_time - runner.statistics.timespan_in_seconds}"
@@ -0,0 +1,14 @@
1
+ require File.dirname(__FILE__) + "/../../test_helper"
2
+
3
+ unit_tests do
4
+ test "dump_schema includes procedures" do
5
+ listener = DeepTest::Database::MysqlSetupListener.new
6
+ listener.expects(:system).with do |command|
7
+ command =~ / -R /
8
+ end
9
+ listener.expects(:master_database_config).returns({})
10
+ listener.expects(:dump_file_name).returns("")
11
+ $?.expects(:success?).returns(true)
12
+ listener.dump_schema
13
+ end
14
+ end
@@ -0,0 +1,209 @@
1
+ require File.dirname(__FILE__) + "/../../test_helper"
2
+
3
+ unit_tests do
4
+ test "dispatch invokes each receiver once" do
5
+ receiver_1, receiver_2 = mock, mock
6
+
7
+ controller = DeepTest::Distributed::DispatchController.new(
8
+ DeepTest::Options.new({:ui => "DeepTest::UI::Null"}),
9
+ [receiver_1, receiver_2]
10
+ )
11
+
12
+ receiver_1.expects(:a_method).with(:args)
13
+ receiver_2.expects(:a_method).with(:args)
14
+
15
+ controller.dispatch(:a_method, :args)
16
+ end
17
+
18
+ test "dispatch returns array of all results" do
19
+ receiver_1, receiver_2 = mock, mock
20
+
21
+ controller = DeepTest::Distributed::DispatchController.new(
22
+ DeepTest::Options.new({:ui => "DeepTest::UI::Null"}),
23
+ [receiver_1, receiver_2]
24
+ )
25
+
26
+ receiver_1.expects(:a_method).returns(:result_1)
27
+ receiver_2.expects(:a_method).returns(:result_2)
28
+
29
+ results = controller.dispatch(:a_method)
30
+ assert_equal 2, results.size
31
+ assert_equal [:result_1, :result_2].to_set, results.to_set
32
+ end
33
+
34
+ test "dispatch calls all receivers in parallel" do
35
+ options = DeepTest::Options.new({:ui => "DeepTest::UI::Null"})
36
+ waiter = Waiter.new(tracker = Tracker.new)
37
+
38
+ Timeout.timeout(1) do
39
+ DeepTest::Distributed::DispatchController.new(options,[tracker, waiter]).
40
+ dispatch(:tracked_method)
41
+ end
42
+
43
+ waiter = Waiter.new(tracker = Tracker.new)
44
+
45
+ Timeout.timeout(1) do
46
+ DeepTest::Distributed::DispatchController.new(options,[waiter, tracker]).
47
+ dispatch(:tracked_method)
48
+ end
49
+ end
50
+
51
+ test "dispatch omits results that are taking too long" do
52
+ receiver = Object.new
53
+ def receiver.__drburi; ""; end
54
+ def receiver.sleep_100_millis
55
+ sleep 0.1
56
+ end
57
+
58
+ controller = DeepTest::Distributed::DispatchController.new(
59
+ DeepTest::Options.new({:ui => "DeepTest::UI::Null", :timeout_in_seconds => 0.05}),
60
+ [receiver]
61
+ )
62
+
63
+ DeepTest.logger.expects(:error)
64
+
65
+ assert_equal [], controller.dispatch(:sleep_100_millis)
66
+ end
67
+
68
+ test "after timeout, no further calls are sent to that receiver" do
69
+ receiver_1, receiver_2 = mock(:__drburi => ""), mock
70
+ receiver_1.expects(:method_call_1).raises(Timeout::Error.new("message"))
71
+ receiver_1.expects(:method_call_2).never
72
+
73
+ receiver_2.expects(:method_call_1)
74
+ receiver_2.expects(:method_call_2)
75
+
76
+ controller = DeepTest::Distributed::DispatchController.new(
77
+ DeepTest::Options.new({:ui => "DeepTest::UI::Null", :timeout_in_seconds => 0.05}),
78
+ [receiver_1, receiver_2]
79
+ )
80
+
81
+ DeepTest.logger.expects(:error)
82
+
83
+ controller.dispatch(:method_call_1)
84
+ controller.dispatch(:method_call_2)
85
+ end
86
+
87
+ test "receiver is dropped when connection is refused" do
88
+ receiver_1, receiver_2 = mock(:__drburi => ""), mock
89
+ receiver_1.expects(:method_call_1).raises(DRb::DRbConnError)
90
+ receiver_1.expects(:method_call_2).never
91
+
92
+ receiver_2.expects(:method_call_1).returns(:value)
93
+ receiver_2.expects(:method_call_2)
94
+
95
+ controller = DeepTest::Distributed::DispatchController.new(
96
+ DeepTest::Options.new({:ui => "DeepTest::UI::Null", :timeout_in_seconds => 0.05}),
97
+ [receiver_1, receiver_2]
98
+ )
99
+
100
+ DeepTest.logger.expects(:error)
101
+
102
+ assert_equal [:value], controller.dispatch(:method_call_1)
103
+
104
+ controller.dispatch(:method_call_2)
105
+ end
106
+
107
+ test "receiver is dropped when any exception occurs" do
108
+ receiver = mock(:__drburi => "")
109
+ receiver.expects(:method_call).raises(Exception)
110
+
111
+ controller = DeepTest::Distributed::DispatchController.new(
112
+ DeepTest::Options.new({:ui => "DeepTest::UI::Null", :timeout_in_seconds => 0.05}),
113
+ [receiver]
114
+ )
115
+
116
+ DeepTest.logger.expects(:error)
117
+
118
+ controller.dispatch(:method_call)
119
+ assert_raises(DeepTest::Distributed::NoDispatchReceiversError) {controller.dispatch(:another_call)}
120
+ end
121
+
122
+ test "no error is printed if dispatching without error" do
123
+ receiver_1 = mock
124
+ receiver_1.expects(:method_call_1).raises(DRb::DRbConnError)
125
+
126
+ controller = DeepTest::Distributed::DispatchController.new(
127
+ DeepTest::Options.new({:ui => "DeepTest::UI::Null", :timeout_in_seconds => 0.05}),
128
+ [receiver_1]
129
+ )
130
+
131
+ DeepTest.logger.expects(:error).never
132
+
133
+ controller.dispatch_with_options(:method_call_1, :ignore_connection_error => true)
134
+ end
135
+
136
+ test "dispatch calls notifies ui of start and stop of dispatch" do
137
+ options = DeepTest::Options.new({:ui => "DeepTest::UI::Null"})
138
+ controller = DeepTest::Distributed::DispatchController.new(
139
+ options, [stub_everything]
140
+ )
141
+
142
+ options.ui_instance.expects(:dispatch_starting).with(:method_name)
143
+ options.ui_instance.expects(:dispatch_finished).with(:method_name)
144
+
145
+ controller.dispatch(:method_name)
146
+ end
147
+
148
+ test "dispatch calls notifies ui dispatch end in case of an error" do
149
+ options = DeepTest::Options.new({:ui => "DeepTest::UI::Null"})
150
+ controller = DeepTest::Distributed::DispatchController.new(
151
+ options, [receiver = mock(:__drburi => '')]
152
+ )
153
+ receiver.expects(:method_name).raises("An Error")
154
+ DeepTest.logger.expects(:error)
155
+
156
+ options.ui_instance.expects(:dispatch_starting).with(:method_name)
157
+ options.ui_instance.expects(:dispatch_finished).with(:method_name)
158
+
159
+ begin
160
+ controller.dispatch(:method_name)
161
+ rescue RuntimeError => e
162
+ raise unless e.message == "An Error"
163
+ end
164
+ end
165
+
166
+ test "error is raised if dispatch to no receivers in attempted" do
167
+ options = DeepTest::Options.new({:ui => "DeepTest::UI::Null"})
168
+ controller = DeepTest::Distributed::DispatchController.new(
169
+ options, []
170
+ )
171
+
172
+ assert_raises(DeepTest::Distributed::NoDispatchReceiversError) do
173
+ controller.dispatch(:any_method)
174
+ end
175
+ end
176
+
177
+ class Tracker
178
+ def initialize
179
+ @mutex = Mutex.new
180
+ @condvar = ConditionVariable.new
181
+ end
182
+
183
+ def tracked_method
184
+ @mutex.synchronize do
185
+ @tracked_method_called = true
186
+ @condvar.broadcast
187
+ end
188
+ end
189
+
190
+ def wait_for_tracked_method
191
+ @mutex.synchronize do
192
+ loop do
193
+ return if @tracked_method_called
194
+ @condvar.wait(@mutex)
195
+ end
196
+ end
197
+ end
198
+ end
199
+
200
+ class Waiter
201
+ def initialize(tracker)
202
+ @tracker = tracker
203
+ end
204
+
205
+ def tracked_method
206
+ @tracker.wait_for_tracked_method
207
+ end
208
+ end
209
+ end