deep_test 1.1.4 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -1,33 +1,25 @@
1
1
  module DeepTest
2
2
  class Options
3
- class Option
4
- attr_reader :name, :default
5
-
6
- def initialize(name, conversion, default)
7
- @name, @conversion, @default = name, conversion, default
8
- end
9
-
10
- def from_command_line(command_line)
11
- command_line =~ /--#{name} (\S+)(\s|$)/
12
- $1.send(@conversion) if $1
13
- end
14
-
15
- def to_command_line(value)
16
- "--#{name} #{value}" if value && value != default
17
- end
18
- end
19
-
20
3
  unless defined?(VALID_OPTIONS)
21
4
  VALID_OPTIONS = [
22
- Option.new(:number_of_workers, :to_i, 2),
23
- Option.new(:pattern, :to_s, nil),
24
- Option.new(:timeout_in_seconds, :to_i, 30),
25
- Option.new(:server_port, :to_i, 6969),
26
- Option.new(:worker_listener, :to_s, "DeepTest::NullWorkerListener"),
5
+ Option.new(:distributed_server, Option::String, nil),
6
+ Option.new(:number_of_workers, Option::Integer, 2),
7
+ Option.new(:metrics_file, Option::String, nil),
8
+ Option.new(:pattern, Option::String, nil),
9
+ Option.new(:server_port, Option::Integer, 6969),
10
+ Option.new(:sync_options, Option::Hash, {}),
11
+ Option.new(:timeout_in_seconds, Option::Integer, 30),
12
+ Option.new(:ui, Option::String, "DeepTest::UI::Console"),
13
+ Option.new(:worker_listener, Option::String, "DeepTest::NullWorkerListener"),
27
14
  ]
28
15
  end
29
16
 
30
17
  attr_accessor *VALID_OPTIONS.map {|o| o.name}
18
+
19
+ def ui=(value)
20
+ @ui = value.to_s
21
+ end
22
+
31
23
  def worker_listener=(value)
32
24
  @worker_listener = value.to_s
33
25
  end
@@ -41,14 +33,35 @@ module DeepTest
41
33
  end
42
34
 
43
35
  def initialize(hash)
36
+ @origin_hostname = Socket.gethostname
44
37
  check_option_keys(hash)
45
38
  VALID_OPTIONS.each do |option|
46
39
  send("#{option.name}=", hash[option.name] || option.default)
47
40
  end
48
41
  end
49
42
 
50
- def new_worker_listener
51
- eval(worker_listener).new
43
+ def gathering_metrics?
44
+ !@metrics_file.nil?
45
+ end
46
+
47
+ def new_listener_list
48
+ listeners = worker_listener.split(',').map do |listener|
49
+ eval(listener).new
50
+ end
51
+ ListenerList.new(listeners)
52
+ end
53
+
54
+ def origin_hostname
55
+ (Socket.gethostname == @origin_hostname) ? 'localhost' : @origin_hostname
56
+ end
57
+
58
+ # Don't store UI instances in the options instance, which will
59
+ # need to be dumped over DRb. UI instances may not be dumpable
60
+ # and we don't want to have to start yet another DRb Server
61
+ #
62
+ UI_INSTANCES = {} unless defined?(UI_INSTANCES)
63
+ def ui_instance
64
+ UI_INSTANCES[self] ||= eval(ui).new(self)
52
65
  end
53
66
 
54
67
  def to_command_line
@@ -60,6 +73,30 @@ module DeepTest
60
73
  command_line.compact.join(' ')
61
74
  end
62
75
 
76
+ def mirror_path(base)
77
+ raise "No source directory specified in sync_options" unless sync_options[:source]
78
+ relative_mirror_path = origin_hostname + sync_options[:source].gsub('/','_')
79
+ "#{base}/#{relative_mirror_path}"
80
+ end
81
+
82
+ def new_workers
83
+ if distributed_server.nil?
84
+ LocalWorkers.new self
85
+ else
86
+ begin
87
+ server = Distributed::TestServer.connect(self)
88
+ Distributed::RemoteWorkerClient.new(self, server, LocalWorkers.new(self))
89
+ rescue => e
90
+ ui_instance.distributed_failover_to_local("connect", e)
91
+ LocalWorkers.new self
92
+ end
93
+ end
94
+ end
95
+
96
+ def server
97
+ Server.remote_reference(origin_hostname, server_port)
98
+ end
99
+
63
100
  protected
64
101
 
65
102
  def check_option_keys(hash)
@@ -1,96 +1,49 @@
1
1
  module DeepTest
2
2
  class ProcessOrchestrator
3
- def self.run(options, runner)
4
- new(options, runner).run
3
+ def self.run(options, workers, runner)
4
+ new(options, workers, runner).run
5
5
  end
6
6
 
7
- def initialize(options, runner)
7
+ def initialize(options, workers, runner)
8
8
  @options = options
9
9
  @runner = runner
10
+ @workers = workers
10
11
  end
11
12
 
12
- def run
13
- stop_zombie_warlocks
14
- start_warlock_server
15
- start_all_workers
16
- start_loader
17
- wait_for_loader_to_be_done
18
- exit_process
19
- ensure
20
- stop_all_warlocks
21
- end
22
-
23
- private
24
-
25
- def exit_process
26
- Kernel.exit($?.success? ? 0 : 1)
27
- end
28
-
29
- def start_all_workers
30
- each_worker do |worker_num|
31
- start_worker(worker_num) do
32
- reseed_random_numbers
33
- reconnect_to_database
34
- worker = DeepTest::Worker.new(worker_num,
35
- RindaBlackboard.new(@options),
36
- @options.new_worker_listener)
37
- worker.run
38
- end
39
- end
40
- end
41
-
42
- def reconnect_to_database
43
- ActiveRecord::Base.connection.reconnect! if defined?(ActiveRecord::Base)
44
- end
45
-
46
- def start_worker(worker_num, &blk)
47
- @warlock.start("worker #{worker_num}", &blk)
48
- end
49
-
50
- def reseed_random_numbers
51
- srand
52
- end
53
-
54
- def wait_for_loader_to_be_done
55
- Process.wait(@loader_pid)
56
- end
57
-
58
- def start_warlock_server
59
- server_ready = false
60
- previous_trap = Signal.trap('USR2') {server_ready = true}
61
-
62
- pid = Process.pid
63
- @warlock.start("server") do
64
- DeepTest::Server.start(@options) do
65
- Process.kill('USR2', pid)
13
+ def run(exit_when_done = true)
14
+ passed = false
15
+
16
+ begin
17
+ server = Server.start(@options)
18
+ @options.new_listener_list.before_starting_workers
19
+ @workers.start_all
20
+ begin
21
+ DeepTest.logger.debug "Loader Starting (#{$$})"
22
+ passed = @runner.process_work_units
23
+ ensure
24
+ shutdown(server)
66
25
  end
26
+ ensure
27
+ DeepTest.logger.debug "ProcessOrchestrator: Stopping Server"
28
+ Server.stop
67
29
  end
68
30
 
69
- Thread.pass until server_ready
70
- ensure
71
- Signal.trap('USR2', previous_trap)
31
+ Kernel.exit(passed ? 0 : 1) if exit_when_done
72
32
  end
73
33
 
74
- def stop_all_warlocks
75
- @warlock.stop_all if @warlock
76
- end
34
+ def shutdown(server)
35
+ DeepTest.logger.debug "ProcessOrchestrator: Shutting Down"
36
+ server.done_with_work
77
37
 
78
- def each_worker
79
- @options.number_of_workers.to_i.times { |worker_num| yield worker_num }
80
- end
81
-
82
- def stop_zombie_warlocks
83
- @warlock = DeepTest::Warlock.new
84
- Signal.trap("HUP") { warlock.stop_all; exit 0 }
85
- end
86
-
87
- def start_loader
88
- @loader_pid = fork do
89
- DeepTest.logger.debug "Loader Starting (#{$$})"
90
- passed = @runner.process_work_units
91
- exit(passed ? 0 : 1)
38
+ first_exception = $!
39
+ begin
40
+ DeepTest.logger.debug "ProcessOrchestrator: Stopping Workers"
41
+ @workers.stop_all
42
+ rescue DRb::DRbConnError
43
+ # Workers must have already stopped
44
+ rescue Exception => e
45
+ raise first_exception || e
92
46
  end
93
47
  end
94
-
95
48
  end
96
49
  end
@@ -1,3 +1,6 @@
1
+ require "socket"
2
+
3
+ require File.dirname(__FILE__) + "/option"
1
4
  require File.dirname(__FILE__) + "/options"
2
5
  require File.dirname(__FILE__) + "/test_task"
3
6
  require File.dirname(__FILE__) + "/rspec_detector"
@@ -0,0 +1,36 @@
1
+ module DeepTest
2
+ class ResultReader
3
+ def initialize(blackboard)
4
+ @blackboard = blackboard
5
+ end
6
+
7
+ def read(original_work_units_by_id)
8
+ work_units_by_id = original_work_units_by_id.dup
9
+ errors = 0
10
+
11
+ begin
12
+ until errors == work_units_by_id.size
13
+ Thread.pass
14
+ result = @blackboard.take_result
15
+ next if result.nil?
16
+
17
+ if Worker::Error === result
18
+ puts result
19
+ errors += 1
20
+ else
21
+ if result.respond_to?(:output) && (output = result.output)
22
+ print output
23
+ end
24
+
25
+ work_unit = work_units_by_id.delete(result.identifier)
26
+ yield [work_unit, result]
27
+ end
28
+ end
29
+ rescue Server::ResultOverdueError
30
+ DeepTest.logger.error("Results are overdue from server, ending run")
31
+ end
32
+
33
+ work_units_by_id
34
+ end
35
+ end
36
+ end
@@ -1,17 +1,24 @@
1
1
  module DeepTest
2
2
  class RSpecDetector
3
3
  def self.if_rspec_available
4
- begin
5
- require 'rubygems'
6
- rescue LoadError
7
- else
8
- begin
9
- gem 'rspec'
10
- rescue Gem::LoadError
11
- else
4
+ require "rubygems"
5
+ # requiring 'spec' directly blows up unit-record
6
+ require "spec/version"
7
+ if defined?(::Spec)
8
+ if ::Spec::VERSION::MAJOR >= 1 &&
9
+ ::Spec::VERSION::MINOR >= 1 &&
10
+ ::Spec::VERSION::TINY >= 3
12
11
  yield
12
+ else
13
+ require 'spec/rake/spectask'
14
+ ::Spec::Rake::SpecTask.class_eval do
15
+ def deep_test(options)
16
+ raise "* DeepTest RSpec support requires RSpec 1.1.3 or greater"
17
+ end
18
+ end
13
19
  end
14
20
  end
21
+ rescue LoadError, Gem::LoadError
15
22
  end
16
23
  end
17
24
  end
@@ -1,11 +1,75 @@
1
1
  module DeepTest
2
2
  class Server
3
3
  def self.start(options)
4
- DRb.start_service
5
- Rinda::RingServer.new(Rinda::TupleSpace.new, options.server_port)
4
+ server = new(options)
5
+ DRb.start_service("druby://0.0.0.0:#{options.server_port}", server)
6
6
  DeepTest.logger.info "Started DeepTest service at #{DRb.uri}"
7
- yield if block_given?
8
- DRb.thread.join
7
+ server
8
+ end
9
+
10
+ def self.stop
11
+ DRb.stop_service
12
+ end
13
+
14
+ def self.remote_reference(address, port)
15
+ DRb.start_service
16
+ blackboard = DRbObject.new_with_uri("druby://#{address}:#{port}")
17
+ DeepTest.logger.debug "Connecting to DeepTest server at #{blackboard.__drburi}"
18
+ blackboard
19
+ end
20
+
21
+ def initialize(options)
22
+ @options = options
23
+ @work_queue = Queue.new
24
+ @result_queue = Queue.new
25
+
26
+ if Metrics::Gatherer.enabled?
27
+ require File.dirname(__FILE__) + "/metrics/queue_lock_wait_time_measurement"
28
+ @work_queue.extend Metrics::QueueLockWaitTimeMeasurement
29
+ @result_queue.extend Metrics::QueueLockWaitTimeMeasurement
30
+ Metrics::Gatherer.section("server queue lock wait times") do |s|
31
+ s.measurement("work queue total pop wait time", @work_queue.total_pop_time)
32
+ s.measurement("work queue total push wait time", @work_queue.total_push_time)
33
+ s.measurement("result queue total pop wait time", @result_queue.total_pop_time)
34
+ s.measurement("result queue total push wait time", @result_queue.total_push_time)
35
+ end
36
+ end
37
+ end
38
+
39
+ def done_with_work
40
+ @done_with_work = true
41
+ end
42
+
43
+ def take_result
44
+ Timeout.timeout(@options.timeout_in_seconds, ResultOverdueError) do
45
+ @result_queue.pop
46
+ end
47
+ end
48
+
49
+ def take_work
50
+ raise NoWorkUnitsRemainingError if @done_with_work
51
+
52
+ @work_queue.pop(true)
53
+ rescue ThreadError => e
54
+ if e.message == "queue empty"
55
+ raise NoWorkUnitsAvailableError
56
+ else
57
+ raise
58
+ end
59
+ end
60
+
61
+ def write_result(result)
62
+ @result_queue.push result
63
+ nil
64
+ end
65
+
66
+ def write_work(work_unit)
67
+ @work_queue.push work_unit
68
+ nil
9
69
  end
70
+
71
+ class NoWorkUnitsAvailableError < StandardError; end
72
+ class NoWorkUnitsRemainingError < StandardError; end
73
+ class ResultOverdueError < StandardError; end
10
74
  end
11
75
  end
@@ -3,6 +3,7 @@ require 'spec/example/example_group_methods'
3
3
  require 'spec/rake/spectask'
4
4
 
5
5
  require File.dirname(__FILE__) + "/spec/extensions/example_group_methods"
6
+ require File.dirname(__FILE__) + "/spec/extensions/example_methods"
6
7
  require File.dirname(__FILE__) + "/spec/extensions/spec_task"
7
8
  require File.dirname(__FILE__) + "/spec/extensions/options"
8
9
  require File.dirname(__FILE__) + "/spec/extensions/reporter"
@@ -1,38 +1,64 @@
1
1
  module Spec
2
2
  module Example
3
3
  module ExampleGroupMethods
4
- PREPEND_BEFORE = instance_method(:prepend_before) unless defined?(PREPEND_BEFORE)
5
- APPEND_BEFORE = instance_method(:append_before) unless defined?(APPEND_BEFORE)
6
- PREPEND_AFTER = instance_method(:prepend_after) unless defined?(PREPEND_AFTER)
7
- APPEND_AFTER = instance_method(:append_after) unless defined?(APPEND_AFTER)
4
+ class << self
5
+ def assign_instance_method_to_constant(proposed_constant)
6
+ method_sym = proposed_constant.to_s.downcase
7
+
8
+ unless const_defined?(proposed_constant)
9
+ const_set(proposed_constant, instance_method(method_sym))
10
+ end
11
+ end
12
+
13
+ private :assign_instance_method_to_constant
14
+ end
15
+
16
+ assign_instance_method_to_constant :PREPEND_BEFORE
17
+ assign_instance_method_to_constant :APPEND_BEFORE
18
+ assign_instance_method_to_constant :PREPEND_AFTER
19
+ assign_instance_method_to_constant :APPEND_AFTER
8
20
 
9
21
  def prepend_before(*args, &block)
10
22
  check_filter_args(args)
11
- PREPEND_BEFORE.bind(self).call(*args, &block)
23
+ call_regular_instance_method :prepend_before, *args, &block
12
24
  end
13
25
 
14
26
  def append_before(*args, &block)
15
27
  check_filter_args(args)
16
- APPEND_BEFORE.bind(self).call(*args, &block)
28
+ call_regular_instance_method :append_before, *args, &block
17
29
  end
30
+
18
31
  alias_method :before, :append_before
19
32
 
20
33
  def prepend_after(*args, &block)
21
34
  check_filter_args(args)
22
- PREPEND_AFTER.bind(self).call(*args, &block)
35
+ call_regular_instance_method :prepend_after, *args, &block
23
36
  end
24
37
 
25
38
  def append_after(*args, &block)
26
39
  check_filter_args(args)
27
- APPEND_AFTER.bind(self).call(*args, &block)
40
+ call_regular_instance_method :append_after, *args, &block
28
41
  end
42
+
29
43
  alias_method :after, :append_after
30
44
 
45
+ private
46
+
47
+ DeepTestAllBlockWarning =
48
+ "Warning: DeepTest will run before(:all) and after(:all) blocks for *every* test that is run. To remove this warning either convert all before/after blocks to each blocks or set $show_deep_test_all_block_warning to false" unless defined?(DeepTestAllBlockWarning)
49
+
50
+ $show_deep_test_all_block_warning = true
51
+
31
52
  def check_filter_args(args)
32
- raise BeforeAfterAllNotSupportedByDeepTestError if args.first == :all
53
+ if args.first == :all && $show_deep_test_all_block_warning
54
+ $show_deep_test_all_block_warning = false
55
+ $stderr.puts DeepTestAllBlockWarning
56
+ end
57
+ end
58
+
59
+ def call_regular_instance_method(sym, *args, &block)
60
+ ExampleGroupMethods.const_get(sym.to_s.upcase).bind(self).call(*args, &block)
33
61
  end
34
-
35
- class BeforeAfterAllNotSupportedByDeepTestError < StandardError; end
36
62
  end
37
63
  end
38
64
  end