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,133 @@
1
+ module DeepTest
2
+ module Metrics
3
+ module QueueLockWaitTimeMeasurement
4
+ attr_reader :total_pop_time, :total_push_time
5
+
6
+ def self.extended(o)
7
+ o.instance_eval do
8
+ alias pop_without_lock_wait_measurement pop
9
+ alias pop pop_with_lock_wait_measurement
10
+
11
+ alias push_without_lock_wait_measurement push
12
+ alias push push_with_lock_wait_measurement
13
+ end
14
+ end
15
+
16
+ def pop_with_lock_wait_measurement(no_wait = false)
17
+ if no_wait
18
+ return measure(:add_pop_time) do
19
+ pop_without_lock_wait_measurement(no_wait)
20
+ end
21
+ else
22
+ begin
23
+ # Measure without waiting to minimize extra time added
24
+ # above locking time
25
+ #
26
+ return measure(:add_pop_time) do
27
+ pop_without_lock_wait_measurement(true)
28
+ end
29
+ rescue ThreadError => e
30
+ if e.message == "queue empty"
31
+ # Normally we would have waiting for a condvar signal,
32
+ # so don't penalize time for locking here again - hence
33
+ # no measure
34
+ return pop_without_lock_wait_measurement(false)
35
+ else
36
+ raise
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ def push_with_lock_wait_measurement(value)
43
+ measure(:add_push_time) do
44
+ push_without_lock_wait_measurement(value)
45
+ end
46
+ end
47
+
48
+ def measure(accumulator)
49
+ start_time = Time.now
50
+ result = yield
51
+ send(accumulator, Time.now - start_time)
52
+ result
53
+ end
54
+
55
+ def add_push_time(time)
56
+ Thread.exclusive do
57
+ @total_push_time ||= 0
58
+ @total_push_time += time
59
+ end
60
+ end
61
+
62
+ def add_pop_time(time)
63
+ Thread.exclusive do
64
+ @total_pop_time ||= 0
65
+ @total_pop_time += time
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+
73
+ if $0 == __FILE__
74
+ require 'thread'
75
+ require 'timeout'
76
+
77
+ thread_count = (ARGV[0] || 10).to_i
78
+ action_count = (ARGV[1] || 100).to_i
79
+
80
+ def test_measurement(thread_count, action_count)
81
+ q = Queue.new
82
+ q.extend DeepTest::Metrics::QueueLockWaitTimeMeasurement
83
+
84
+ threads = []
85
+ thread_count.times do
86
+ threads << Thread.new do
87
+ action_count.times do |i|
88
+ show_progress ".", action_count, i
89
+ q.push 1
90
+ end
91
+ end
92
+ end
93
+
94
+ thread_count.times do
95
+ threads << Thread.new do
96
+ action_count.times do |i|
97
+ show_progress "-", action_count, i
98
+ if rand(2) == 0
99
+ begin
100
+ q.pop(true)
101
+ rescue ThreadError
102
+ end
103
+ else
104
+ begin
105
+ Timeout.timeout(0.01) do
106
+ q.pop
107
+ end
108
+ rescue Timeout::Error
109
+ break
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ threads.each {|t| t.join}
117
+
118
+ puts
119
+ puts "Push Time: #{q.total_push_time}"
120
+ puts "Pop Time: #{q.total_pop_time}"
121
+ end
122
+
123
+ def show_progress(s, total, current)
124
+ if (current % (total / 5)) == 0
125
+ $stdout.print s
126
+ $stdout.flush
127
+ end
128
+ end
129
+
130
+ start_time = Time.now
131
+ test_measurement(thread_count, action_count)
132
+ puts "Total Run Time: #{Time.now - start_time}"
133
+ end
@@ -0,0 +1,62 @@
1
+ module DeepTest
2
+ #
3
+ # Listener that implements no-ops for all callbacks that DeepTest supports.
4
+ #
5
+ class NullWorkerListener
6
+ #
7
+ # Before DeepTest synchronizes any code during a distributed run,
8
+ # before_sync is called. If DeepTest is not running distributed,
9
+ # before_sync is never called.
10
+ #
11
+ def before_sync
12
+ end
13
+
14
+ #
15
+ # Before DeepTest starts any workers, it instantiates a listener and
16
+ # invokes this method. No other callbacks are made to the listener
17
+ # instance receiving this message.
18
+ #
19
+ def before_starting_workers
20
+ end
21
+
22
+ #
23
+ # A separate listener instance is created in each worker process and
24
+ # notified that the worker is starting. The worker for the process is
25
+ # provided for the listener to use. If you are using 3 workers, this
26
+ # method is invoked 3 times on 3 distinct instances. These instances
27
+ # will also receive the starting_work and finished_work callbacks for
28
+ # the worker provided.
29
+ #
30
+ def starting(worker)
31
+ end
32
+
33
+ #
34
+ # Each time a worker takes a work unit, it calls this method before
35
+ # doing the work. In total, this method will be called as many times
36
+ # as there are work units. The listener instance that received the
37
+ # starting callback with the worker provided here is the same instance
38
+ # that receives this message.
39
+ #
40
+ # Because each work processes work units in a serial fashion, the
41
+ # listener will receive a finished_work message before another
42
+ # starting_work message.
43
+ #
44
+ def starting_work(worker, work_unit)
45
+ end
46
+
47
+ #
48
+ # Each time a worker finishes computing a result for a work unit,
49
+ # it calls this method before sending that result back to the server.
50
+ # In total, this method will be called as many times
51
+ # as there are work units. The listener instance that received the
52
+ # starting callback with the worker provided here is the same instance
53
+ # that receives this message.
54
+ #
55
+ # Because each work processes work units in a serial fashion, the
56
+ # listener will receive a starting_work message before another
57
+ # finished_work message.
58
+ #
59
+ def finished_work(worker, work_unit, result)
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,60 @@
1
+ module DeepTest
2
+ class Option
3
+ attr_reader :name, :default
4
+
5
+ def initialize(name, type, default)
6
+ @name, @type, @default = name, type, default
7
+ end
8
+
9
+ def from_command_line(command_line)
10
+ command_line =~ /--#{name} (\S+)(\s|$)/
11
+ @type.from_string($1) if $1
12
+ end
13
+
14
+ def to_command_line(value)
15
+ "--#{name} #{@type.to_string(value)}" if value && value != default
16
+ end
17
+
18
+ module Hash
19
+ def self.to_string(hash)
20
+ pairs = []
21
+ hash.each do |key, value|
22
+ value = value.gsub(/ /,'%20') if (::String === value)
23
+ pairs << "#{key}:#{value.inspect}"
24
+ end
25
+ pairs.join(",")
26
+ end
27
+
28
+ def self.from_string(string)
29
+ hash = {}
30
+ string.split(/,/).each do |pair|
31
+ key, unevaled_value = pair.split(/:/)
32
+ value = eval(unevaled_value)
33
+ value = value.gsub(/%20/, " ") if ::String === value
34
+ hash[key.to_sym] = value
35
+ end
36
+ hash
37
+ end
38
+ end
39
+
40
+ module Integer
41
+ def self.to_string(i)
42
+ i.to_s
43
+ end
44
+
45
+ def self.from_string(s)
46
+ s.to_i
47
+ end
48
+ end
49
+
50
+ module String
51
+ def self.to_string(s)
52
+ s
53
+ end
54
+
55
+ def self.from_string(s)
56
+ s
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,110 @@
1
+ module DeepTest
2
+ class Options
3
+ unless defined?(VALID_OPTIONS)
4
+ VALID_OPTIONS = [
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"),
14
+ ]
15
+ end
16
+
17
+ attr_accessor *VALID_OPTIONS.map {|o| o.name}
18
+
19
+ def ui=(value)
20
+ @ui = value.to_s
21
+ end
22
+
23
+ def worker_listener=(value)
24
+ @worker_listener = value.to_s
25
+ end
26
+
27
+ def self.from_command_line(command_line)
28
+ hash = {}
29
+ VALID_OPTIONS.each do |option|
30
+ hash[option.name] = option.from_command_line(command_line)
31
+ end
32
+ new(hash)
33
+ end
34
+
35
+ def initialize(hash)
36
+ @origin_hostname = Socket.gethostname
37
+ check_option_keys(hash)
38
+ VALID_OPTIONS.each do |option|
39
+ send("#{option.name}=", hash[option.name] || option.default)
40
+ end
41
+ end
42
+
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)
65
+ end
66
+
67
+ def to_command_line
68
+ command_line = []
69
+ VALID_OPTIONS.each do |option|
70
+ value = send(option.name)
71
+ command_line << option.to_command_line(value)
72
+ end
73
+ command_line.compact.join(' ')
74
+ end
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
+
100
+ protected
101
+
102
+ def check_option_keys(hash)
103
+ hash.keys.each do |key|
104
+ raise InvalidOptionError.new(key) unless VALID_OPTIONS.any? {|o| o.name == key.to_sym}
105
+ end
106
+ end
107
+
108
+ class InvalidOptionError < StandardError; end
109
+ end
110
+ end
@@ -0,0 +1,49 @@
1
+ module DeepTest
2
+ class ProcessOrchestrator
3
+ def self.run(options, workers, runner)
4
+ new(options, workers, runner).run
5
+ end
6
+
7
+ def initialize(options, workers, runner)
8
+ @options = options
9
+ @runner = runner
10
+ @workers = workers
11
+ end
12
+
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)
25
+ end
26
+ ensure
27
+ DeepTest.logger.debug "ProcessOrchestrator: Stopping Server"
28
+ Server.stop
29
+ end
30
+
31
+ Kernel.exit(passed ? 0 : 1) if exit_when_done
32
+ end
33
+
34
+ def shutdown(server)
35
+ DeepTest.logger.debug "ProcessOrchestrator: Shutting Down"
36
+ server.done_with_work
37
+
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
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,11 @@
1
+ require "socket"
2
+
3
+ require File.dirname(__FILE__) + "/option"
4
+ require File.dirname(__FILE__) + "/options"
5
+ require File.dirname(__FILE__) + "/test_task"
6
+ require File.dirname(__FILE__) + "/rspec_detector"
7
+
8
+ DeepTest::RSpecDetector.if_rspec_available do
9
+ require 'spec/rake/spectask'
10
+ require File.dirname(__FILE__) + "/spec/extensions/spec_task"
11
+ end
@@ -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