dtr 0.0.4 → 1.0.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 (93) hide show
  1. data/CHANGES +7 -0
  2. data/LICENSE.txt +203 -0
  3. data/README.rdoc +208 -0
  4. data/Rakefile +64 -205
  5. data/TODO +8 -2
  6. data/bin/dtr +27 -60
  7. data/dtr.gemspec +8 -11
  8. data/lib/dtr.rb +5 -94
  9. data/lib/dtr/agent.rb +38 -0
  10. data/lib/dtr/agent/brain.rb +57 -0
  11. data/lib/dtr/agent/herald.rb +60 -0
  12. data/lib/dtr/agent/runner.rb +87 -0
  13. data/lib/dtr/agent/sync_codebase.rb +44 -0
  14. data/lib/dtr/agent/sync_logger.rb +70 -0
  15. data/lib/dtr/agent/test_case.rb +53 -0
  16. data/lib/dtr/agent/test_unit.rb +40 -0
  17. data/lib/dtr/agent/worker.rb +89 -0
  18. data/lib/dtr/agent/working_env_ext.rb +47 -0
  19. data/lib/dtr/facade.rb +65 -0
  20. data/lib/dtr/master.rb +40 -0
  21. data/lib/dtr/monitor.rb +95 -0
  22. data/lib/dtr/raketasks.rb +155 -22
  23. data/lib/dtr/shared.rb +24 -0
  24. data/lib/dtr/shared/adapter.rb +115 -0
  25. data/lib/dtr/shared/configuration.rb +104 -0
  26. data/lib/dtr/shared/message_decorator.rb +28 -0
  27. data/lib/dtr/shared/ruby_ext.rb +129 -0
  28. data/lib/dtr/shared/service.rb +19 -0
  29. data/lib/dtr/shared/service/agent.rb +37 -0
  30. data/lib/dtr/shared/service/file.rb +28 -0
  31. data/lib/dtr/shared/service/rinda.rb +48 -0
  32. data/lib/dtr/shared/service/runner.rb +34 -0
  33. data/lib/dtr/shared/service/working_env.rb +28 -0
  34. data/lib/dtr/shared/sync_codebase.rb +18 -0
  35. data/lib/dtr/shared/sync_codebase/copiable_package.rb +40 -0
  36. data/lib/dtr/shared/sync_codebase/master_ext.rb +40 -0
  37. data/lib/dtr/shared/sync_codebase/package.rb +53 -0
  38. data/lib/dtr/shared/sync_codebase/sync_service.rb +36 -0
  39. data/lib/dtr/shared/sync_logger.rb +64 -0
  40. data/lib/dtr/shared/utils.rb +17 -0
  41. data/lib/dtr/shared/utils/cmd.rb +30 -0
  42. data/lib/dtr/shared/utils/env_store.rb +60 -0
  43. data/lib/dtr/shared/utils/logger.rb +87 -0
  44. data/lib/dtr/shared/working_env.rb +38 -0
  45. data/lib/dtr/test_unit.rb +9 -275
  46. data/lib/dtr/test_unit/drb_test_runner.rb +48 -0
  47. data/lib/dtr/test_unit/injection.rb +29 -0
  48. data/lib/dtr/test_unit/test_case_injection.rb +37 -0
  49. data/lib/dtr/test_unit/test_suite_injection.rb +24 -0
  50. data/lib/dtr/test_unit/testrunnermediator_injection.rb +72 -0
  51. data/lib/dtr/test_unit/thread_safe_test_result.rb +38 -0
  52. data/lib/dtr/test_unit/worker_club.rb +72 -0
  53. data/lib/dtr/test_unit_injection.rb +1 -2
  54. data/test/acceptance/agent_working_env_test.rb +86 -0
  55. data/test/acceptance/dtr_package_task_test.rb +36 -0
  56. data/test/acceptance/general_test.rb +331 -0
  57. data/test/acceptance/raketasks_test.rb +23 -0
  58. data/test/acceptance/sync_codebase_test.rb +66 -0
  59. data/test/acceptance/sync_logger_test.rb +32 -0
  60. data/test/agent_helper.rb +37 -0
  61. data/test/logger_stub.rb +34 -0
  62. data/test/test_helper.rb +71 -0
  63. data/test/unit/adapter_test.rb +149 -0
  64. data/test/unit/configuration_test.rb +44 -0
  65. data/test/unit/facade_test.rb +41 -0
  66. data/test/unit/logger_test.rb +72 -0
  67. data/test/unit/test_unit_test.rb +26 -0
  68. data/test/unit/working_env_test.rb +71 -0
  69. data/testdata/Rakefile +11 -0
  70. data/testdata/a_failed_test_case.rb +8 -0
  71. data/testdata/a_file_system_test_case.rb +8 -0
  72. data/testdata/a_test_case.rb +13 -0
  73. data/testdata/a_test_case2.rb +6 -0
  74. data/testdata/an_error_test_case.rb +9 -0
  75. data/testdata/another_project/Rakefile +6 -0
  76. data/testdata/another_project/passed_test_case.rb +7 -0
  77. data/testdata/hacked_run_method_test_case.rb +15 -0
  78. data/testdata/is_required_by_a_test.rb +9 -0
  79. data/testdata/lib/lib_test_case.rb +7 -0
  80. data/testdata/package_task_test_rakefile +8 -0
  81. data/testdata/raketasks/Rakefile +7 -0
  82. data/testdata/raketasks/success_test_case.rb +6 -0
  83. data/testdata/scenario_test_case.rb +34 -0
  84. data/testdata/setup_agent_env_test_case.rb +9 -0
  85. data/testdata/sleep_3_secs_test_case.rb +9 -0
  86. data/testdata/verify_dir_pwd/Rakefile +6 -0
  87. data/testdata/verify_dir_pwd/verify_dir_pwd_test_case.rb +10 -0
  88. metadata +101 -34
  89. data/README +0 -291
  90. data/install.rb +0 -88
  91. data/lib/dtr/base.rb +0 -172
  92. data/lib/dtr/runner.rb +0 -270
  93. data/lib/dtr/service_provider.rb +0 -160
@@ -0,0 +1,87 @@
1
+ # Copyright (c) 2007-2008 Li Xiao <iam@li-xiao.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module DTR
16
+ module Agent
17
+ class Runner
18
+ include DRbUndumped
19
+ include Service::Runner
20
+
21
+ def self.start(name, env)
22
+ self.new(name, env).start
23
+ DRb.thread.join if DRb.thread
24
+ end
25
+
26
+ attr_reader :name, :identifier
27
+
28
+ def initialize(name, env)
29
+ @name = name
30
+ @identifier = env[:identifier]
31
+ @env = env
32
+ end
33
+
34
+ def start
35
+ #start service first, so that all logs can be sync with master process
36
+ start_service
37
+ DTR.info("=> Starting runner #{name} at #{Dir.pwd}, pid: #{Process.pid}")
38
+ init_environment
39
+ provide
40
+ DTR.info {"=> Runner #{name} provided"}
41
+ rescue Exception
42
+ DTR.error($!.message)
43
+ DTR.error($!.backtrace.join("\n"))
44
+ end
45
+
46
+ def init_environment
47
+ DTR.info {"#{name}: Initialize working environment..."}
48
+ ENV['DTR_RUNNER_NAME'] = name
49
+
50
+ @env[:libs].select{ |lib| !$LOAD_PATH.include?(lib) && File.exists?(lib) }.each do |lib|
51
+ $LOAD_PATH << lib
52
+ DTR.debug {"#{name}: appended lib: #{lib}"}
53
+ end
54
+ DTR.info {"#{name}: libs loaded"}
55
+ DTR.debug {"#{name}: $LOAD_PATH: #{$LOAD_PATH.inspect}"}
56
+
57
+ @env[:files].each do |f|
58
+ begin
59
+ load f unless f =~ /^-/
60
+ DTR.debug {"#{name}: loaded #{f}"}
61
+ rescue LoadError => e
62
+ DTR.error {"#{name}: No such file to load -- #{f}"}
63
+ DTR.debug {"Environment: #{@env}"}
64
+ end
65
+ end
66
+ DTR.info {"#{name}: test files loaded"}
67
+ end
68
+
69
+ def run(test, result, &progress_block)
70
+ DTR.debug {"#{name}: running #{test}..."}
71
+ Agent::TestCase.new(test, result, &progress_block).run
72
+ DTR.debug {"#{name}: done #{test}"}
73
+ ensure
74
+ provide
75
+ DTR.debug {"=> Runner #{name} provided"}
76
+ end
77
+
78
+ def provide
79
+ provide_runner(self)
80
+ end
81
+
82
+ def to_s
83
+ "Runner #{@name}"
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,44 @@
1
+ # Copyright (c) 2007-2008 Li Xiao <iam@li-xiao.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module DTR
16
+ module SyncCodebase
17
+ module WorkingEnvExt
18
+ include SyncService
19
+
20
+ def self.included(base)
21
+ base.alias_method_chain :setup_env, :sync_codebase
22
+ base.alias_method_chain :working_dir, :sync_codebase
23
+ end
24
+
25
+ def setup_env_with_sync_codebase(setup_env_cmd)
26
+ unless same_working_dir_with_master_process?
27
+ Dir.chdir(working_dir_without_sync_codebase) do
28
+ sync_codebase
29
+ end
30
+ end
31
+ setup_env_without_sync_codebase(setup_env_cmd)
32
+ end
33
+
34
+ def working_dir_with_sync_codebase
35
+ same_working_dir_with_master_process? ? Dir.pwd : File.join(working_dir_without_sync_codebase, package_name)
36
+ end
37
+
38
+ private
39
+ def same_working_dir_with_master_process?
40
+ self[:host] == Socket.gethostname && self[:pwd] == Dir.pwd
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,70 @@
1
+ # Copyright (c) 2007-2008 Li Xiao <iam@li-xiao.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module DTR
16
+ module SyncLogger
17
+
18
+ # Synchronizer loads SyncLogger provided by master process from Rinda server.
19
+ # Any process need sync logs with master process should start service first,
20
+ # so that local logger could be replaced by SyncLogger.
21
+ # For logs would be sent back to master process, all messages would be sent
22
+ # as string message. The message logged as block would be converted to string
23
+ # message.
24
+ module Synchronizer
25
+ def self.included(base)
26
+ base.alias_method_chain :start_service, :sync_logger
27
+ end
28
+
29
+ def start_service_with_sync_logger
30
+ start_service_without_sync_logger
31
+ if logger_tuple = lookup_ring.read_all([:logger, nil]).first
32
+ sync_logger = logger_tuple[1]
33
+ DTR.logger = MessageDecoratedLogger.new(sync_logger)
34
+ end
35
+ end
36
+ end
37
+
38
+ class MessageDecoratedLogger
39
+ include MessageDecorator
40
+
41
+ def initialize(logger)
42
+ @logger = logger
43
+ end
44
+
45
+ def debug(message=nil, &block)
46
+ with_decorating_message(:debug, message, &block)
47
+ end
48
+
49
+ def info(message=nil, &block)
50
+ with_decorating_message(:info, message, &block)
51
+ end
52
+
53
+ def error(message=nil, &block)
54
+ with_decorating_message(:error, message, &block)
55
+ end
56
+
57
+ def level
58
+ @logger_level ||= @logger.level
59
+ end
60
+
61
+ private
62
+ def with_decorating_message(level, msg, &block)
63
+ raise 'Should not use block to send log remotely' if block_given?
64
+ @logger.send(level, decorate_message(msg))
65
+ end
66
+ end
67
+ end
68
+
69
+ Service::Rinda.send(:include, SyncLogger::Synchronizer)
70
+ end
@@ -0,0 +1,53 @@
1
+ # Copyright (c) 2007-2008 Li Xiao <iam@li-xiao.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module DTR
16
+ module Agent
17
+ class UnknownTestError < StandardError
18
+ end
19
+ class TestCase
20
+ def initialize(test, result, &progress_block)
21
+ @test = test
22
+ @result = result
23
+ @progress_block = progress_block
24
+ end
25
+
26
+ def run
27
+ if @test.is_a?(DRb::DRbUnknown)
28
+ add_error(UnknownTestError.new("No such test loaded: #{@test.name}"))
29
+ else
30
+ @test.run(@result, &@progress_block)
31
+ end
32
+ rescue DRb::DRbConnError => e
33
+ msg = "Rescued DRb::DRbConnError(#{e.message}), while running test: #{test}. The master process may be stopped."
34
+ DTR.do_println(msg)
35
+ DTR.info {msg}
36
+ rescue Exception => e
37
+ unexpected_error(e)
38
+ end
39
+
40
+ def unexpected_error(e)
41
+ DTR.error "Unexpected exception: #{e.message}"
42
+ DTR.error e.backtrace.join("\n")
43
+ add_error(e)
44
+ end
45
+
46
+ def add_error(e)
47
+ @result.add_error(Test::Unit::Error.new(@test.name, RemoteError.new(e)))
48
+ @result.add_run
49
+ @progress_block.call(Test::Unit::TestCase::FINISHED, @test.name)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,40 @@
1
+ # Copyright (c) 2007-2008 Li Xiao <iam@li-xiao.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'test/unit'
16
+ require 'test/unit/testcase'
17
+
18
+ module DTR
19
+ module Agent
20
+ module TestCaseExt
21
+ include MessageDecorator
22
+ def self.included(base)
23
+ base.alias_method_chain :add_failure, :decorating_source
24
+ base.alias_method_chain :add_error, :decorating_source
25
+ end
26
+
27
+ def add_error_with_decorating_source(exception)
28
+ add_error_without_decorating_source(DTR::RemoteError.new(exception))
29
+ end
30
+
31
+ def add_failure_with_decorating_source(message, all_locations=caller())
32
+ add_failure_without_decorating_source(decorate_message(message, 'Assertion failure'), all_locations)
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ # set run to true first, so that test auto runner wouldn't work
39
+ Test::Unit.run = true
40
+ Test::Unit::TestCase.send(:include, DTR::Agent::TestCaseExt)
@@ -0,0 +1,89 @@
1
+ # Copyright (c) 2007-2008 Li Xiao <iam@li-xiao.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module DTR
16
+
17
+ module Agent
18
+ # Worker works during one dtr test task running.
19
+ # Worker manages Herald & Runner processes life cycle.
20
+ class Worker
21
+ def initialize(runner_names, agent_env_setup_cmd)
22
+ @runner_names = runner_names.is_a?(Array) ? runner_names : [runner_names.to_s]
23
+ @agent_env_setup_cmd = agent_env_setup_cmd
24
+ @runner_pids = []
25
+ @herald = nil
26
+ @working_env_key = :working_env
27
+ @env_store = EnvStore.new
28
+ end
29
+
30
+ def launch
31
+ DTR.info {"=> Agent worker started at: #{Dir.pwd}, pid: #{Process.pid}"}
32
+ setup
33
+ begin
34
+ run
35
+ ensure
36
+ teardown
37
+ DTR.info {"Agent worker is dieing"}
38
+ end
39
+ end
40
+
41
+ private
42
+ def setup
43
+ @env_store[@working_env_key] = nil
44
+ end
45
+
46
+ def teardown
47
+ unless @runner_pids.blank?
48
+ @runner_pids.each{ |pid| DTR.kill_process pid }
49
+ DTR.info {"=> All runners(#{@runner_pids.join(", ")}) were killed." }
50
+ @runner_pids = []
51
+ end
52
+ if @herald
53
+ DTR.kill_process @herald
54
+ @herald = nil
55
+ DTR.info {"=> Herald is killed."}
56
+ end
57
+ end
58
+
59
+ def run
60
+ herald
61
+ runners
62
+ DTR.info {"=> All agent worker sub processes exited."}
63
+ end
64
+
65
+ def herald
66
+ @herald = DTR.fork_process { Herald.new @working_env_key, @agent_env_setup_cmd, @runner_names }
67
+ Process.waitpid @herald
68
+ exit(-1) unless $?.exitstatus == 0
69
+ end
70
+
71
+ def runners
72
+ working_env = @env_store[@working_env_key]
73
+
74
+ @runner_names.each do |name|
75
+ @runner_pids << DTR.fork_process {
76
+ at_exit {
77
+ # exit anyway, for DRb may hang on the process to be a deadwalk
78
+ exit!
79
+ }
80
+ working_env.within do
81
+ Runner.start name, working_env
82
+ end
83
+ }
84
+ end
85
+ Process.waitall
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,47 @@
1
+ # Copyright (c) 2007-2008 Li Xiao <iam@li-xiao.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module DTR
16
+ module Agent
17
+ # Provides working environment setup beheaviours for Herald & Runner process
18
+ module WorkingEnvExt
19
+ def within
20
+ ENV['DTR_MASTER_ENV'] = self[:dtr_master_env]
21
+ Dir.chdir(working_dir) do
22
+ yield
23
+ end
24
+ end
25
+
26
+ def setup_env(setup_env_cmd)
27
+ within do
28
+ Cmd.execute(setup_env_cmd || self[:agent_env_setup_cmd])
29
+ end
30
+ end
31
+
32
+ private
33
+ def escape(str)
34
+ str.gsub(/[^a-zA-Z0-9]/, '_')
35
+ end
36
+
37
+ def working_dir
38
+ return @working_dir if defined?(@working_dir)
39
+ project_specific_len = 20
40
+ project_name = self[:pwd].length > project_specific_len ? self[:pwd][-project_specific_len..-1] : self[:pwd]
41
+ @working_dir = File.join(escape(self[:host]), escape(project_name))
42
+ FileUtils.mkdir_p(@working_dir)
43
+ @working_dir
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,65 @@
1
+ # Copyright (c) 2007-2008 Li Xiao <iam@li-xiao.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module DTR
16
+ module Facade
17
+ def start_agent
18
+ launch_agent(DTR_AGENT_OPTIONS[:runners], DTR_AGENT_OPTIONS[:agent_env_setup_cmd])
19
+ end
20
+
21
+ def launch_agent(names, setup=nil)
22
+ require 'dtr/agent'
23
+ names = names || "DTR(#{Time.now})"
24
+ DTR::Agent.start(names, setup)
25
+ end
26
+
27
+ def lib_path
28
+ File.expand_path(File.dirname(__FILE__) + '/../')
29
+ end
30
+
31
+ def broadcast_list=(list)
32
+ require 'dtr/shared'
33
+ DTR.configuration.broadcast_list = list
34
+ DTR.configuration.save
35
+ end
36
+
37
+ def agent_listen_port=(port)
38
+ require 'dtr/shared'
39
+ DTR.configuration.agent_listen_port = port
40
+ DTR.configuration.save
41
+ end
42
+
43
+ def group=(group)
44
+ require 'dtr/shared'
45
+ DTR.configuration.group = group
46
+ DTR.configuration.save
47
+ end
48
+
49
+ def monitor
50
+ require 'dtr/monitor'
51
+ DTR.logger('dtr_monitor.log')
52
+ Monitor.new.start
53
+ end
54
+
55
+ # For safe fork & kill sub process, should use Process.kill and Process.fork
56
+ # At least have problem on ruby 1.8.6 114 with Kernel#kill & fork
57
+ def kill_process(pid)
58
+ Process.kill 'TERM', pid rescue nil
59
+ end
60
+
61
+ def fork_process(&block)
62
+ Process.fork(&block)
63
+ end
64
+ end
65
+ end