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
@@ -8,6 +8,14 @@ module DeepTest
8
8
  ensure
9
9
  $stdout = old_stdout if old_stdout
10
10
  end
11
+
12
+ def capture_stderr
13
+ old_stderr, $stderr = $stderr, StringIO.new
14
+ yield
15
+ $stderr.string
16
+ ensure
17
+ $stderr = old_stderr if old_stderr
18
+ end
11
19
 
12
20
  def retrying(description = nil, times = 5)
13
21
  i = 0
@@ -0,0 +1,17 @@
1
+ module DeepTest
2
+ class ListenerList
3
+ attr_reader :listeners
4
+
5
+ def initialize(listeners)
6
+ @listeners = listeners
7
+ end
8
+
9
+ NullWorkerListener.instance_methods(false).each do |event|
10
+ eval <<-end_src
11
+ def #{event}(*args)
12
+ @listeners.each {|l| l.#{event}(*args)}
13
+ end
14
+ end_src
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,55 @@
1
+ module DeepTest
2
+ class LocalWorkers
3
+ def initialize(options)
4
+ @options = options
5
+ @warlock = Warlock.new
6
+ end
7
+
8
+ def load_files(files)
9
+ files.each {|f| load f}
10
+ end
11
+
12
+ def server
13
+ @options.server
14
+ end
15
+
16
+ def start_all
17
+ each_worker do |worker_num|
18
+ start_worker(worker_num) do
19
+ reseed_random_numbers
20
+ reconnect_to_database
21
+ worker = DeepTest::Worker.new(worker_num,
22
+ server,
23
+ @options.new_listener_list)
24
+ worker.run
25
+ end
26
+ end
27
+ end
28
+
29
+ def stop_all
30
+ @warlock.stop_all
31
+ end
32
+
33
+ def number_of_workers
34
+ @options.number_of_workers
35
+ end
36
+
37
+ private
38
+
39
+ def reconnect_to_database
40
+ ActiveRecord::Base.connection.reconnect! if defined?(ActiveRecord::Base)
41
+ end
42
+
43
+ def start_worker(worker_num, &blk)
44
+ @warlock.start("worker #{worker_num}", &blk)
45
+ end
46
+
47
+ def reseed_random_numbers
48
+ srand
49
+ end
50
+
51
+ def each_worker
52
+ number_of_workers.to_i.times { |worker_num| yield worker_num }
53
+ end
54
+ end
55
+ end
@@ -2,8 +2,16 @@ module DeepTest
2
2
  class Logger < ::Logger
3
3
  def initialize(*args)
4
4
  super
5
- self.formatter = proc { |severity, time, progname, msg| "#{msg}\n" }
6
- self.level = Logger::INFO
5
+ self.formatter = proc { |severity, time, progname, msg| "[DeepTest] #{msg}\n" }
6
+ self.level = configured_log_level
7
+ end
8
+
9
+ def configured_log_level
10
+ if ENV['DEEP_TEST_LOG_LEVEL']
11
+ Logger.const_get(ENV['DEEP_TEST_LOG_LEVEL'])
12
+ else
13
+ Logger::INFO
14
+ end
7
15
  end
8
16
  end
9
17
  end
@@ -0,0 +1,44 @@
1
+ module DeepTest
2
+ class MarshallableExceptionWrapper
3
+ attr_reader :classname, :message, :backtrace
4
+
5
+ def initialize(exception)
6
+ @classname = exception.class.name
7
+ @message = exception.message
8
+ @backtrace = exception.backtrace
9
+ end
10
+
11
+ def ==(other)
12
+ classname == other.classname &&
13
+ message == other.message &&
14
+ backtrace == other.backtrace
15
+ end
16
+
17
+ def resolve
18
+ begin
19
+ klass = eval("::" + classname)
20
+ resolved_message = message
21
+ rescue => e
22
+ DeepTest.logger.debug("Unable to load exception class: #{classname}: #{e.message}")
23
+ DeepTest.logger.debug(e.backtrace.join("\n"))
24
+
25
+ klass = UnloadableException
26
+ resolved_message = "#{classname}: #{message}"
27
+ end
28
+
29
+ begin
30
+ resolved_exception = klass.new resolved_message
31
+ rescue => e
32
+ DeepTest.logger.debug("Unable to instantiation exception class: #{classname}: #{e.message}")
33
+ DeepTest.logger.debug(e.backtrace.join("\n"))
34
+
35
+ resolved_exception = UnloadableException.new resolved_message
36
+ end
37
+
38
+ resolved_exception.set_backtrace backtrace
39
+ resolved_exception
40
+ end
41
+ end
42
+
43
+ class UnloadableException < StandardError; end
44
+ end
@@ -0,0 +1,67 @@
1
+ module DeepTest
2
+ module Metrics
3
+ class Gatherer
4
+ def self.setup(options)
5
+ $metrics_gatherer = new(options)
6
+
7
+ at_exit do
8
+ $metrics_gatherer.write_file
9
+ end
10
+ end
11
+
12
+ def self.enabled?
13
+ return false unless $metrics_gatherer
14
+ $metrics_gatherer.enabled?
15
+ end
16
+
17
+ def self.section(title, &block)
18
+ $metrics_gatherer.section(title, &block)
19
+ end
20
+
21
+ def initialize(options)
22
+ @options = options
23
+ @sections = []
24
+ end
25
+
26
+ def enabled?
27
+ !@options.metrics_file.nil?
28
+ end
29
+
30
+ def section(title, &block)
31
+ @sections << Section.new(title, &block) if enabled?
32
+ end
33
+
34
+ def render
35
+ @sections.map {|s| s.render}.join("\n")
36
+ end
37
+
38
+ def write_file
39
+ return unless enabled?
40
+ File.open(@options.metrics_file, "w") do |io|
41
+ io << render
42
+ end
43
+ end
44
+
45
+ class Section
46
+ def initialize(title, &generate_measurements_block)
47
+ @title = title
48
+ @generate_measurements_block = generate_measurements_block
49
+ end
50
+
51
+ def measurement(name, value)
52
+ @measurements << [name, value]
53
+ end
54
+
55
+ def gather_measurements
56
+ @measurements = []
57
+ @generate_measurements_block.call(self)
58
+ end
59
+
60
+ def render
61
+ gather_measurements
62
+ "[#{@title}]\n" + @measurements.map {|name, value| "#{name}: #{value}"}.join("\n")
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -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
@@ -1,11 +1,61 @@
1
1
  module DeepTest
2
+ #
3
+ # Listener that implements no-ops for all callbacks that DeepTest supports.
4
+ #
2
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
+ #
3
30
  def starting(worker)
4
31
  end
5
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
+ #
6
44
  def starting_work(worker, work_unit)
7
45
  end
8
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
+ #
9
59
  def finished_work(worker, work_unit, result)
10
60
  end
11
61
  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