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
@@ -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