actir 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.
@@ -0,0 +1,70 @@
1
+ require 'pathname'
2
+
3
+ module Actir
4
+ class Initializer
5
+
6
+ #config content
7
+ attr_accessor :config
8
+
9
+ def initialize project_path
10
+ $project_path ||= project_path
11
+ $:.unshift($project_path)
12
+ $config = load_config
13
+ load_elements
14
+ end
15
+
16
+ def load_elements
17
+ @elements_path = File.join($project_path, 'elements')
18
+ load_item
19
+ load_components
20
+ load_user
21
+ load_pages
22
+ end
23
+
24
+ def load_config
25
+ @config_path = File.join($project_path, 'config')
26
+ @config = {}
27
+ Dir.glob(File.join @config_path, '**', '*.yaml').select{ |c| c =~ /\.yaml$/ }.each do |config|
28
+ puts "#{config}" if $debug
29
+ #获取配置文件名字
30
+ config =~ /config\/(.*)\.yaml/
31
+ config_name = $1
32
+ @config.store(config_name, Actir::Config.get_content(config))
33
+ end
34
+ @config
35
+ end
36
+
37
+ def load_item
38
+ @item_path = File.join(@elements_path, 'item')
39
+ Dir.glob(File.join @item_path, '**', '*.rb').select {|p| p =~ /\.rb$/}.each do |i|
40
+ puts i if $debug
41
+ require "#{i}"
42
+ end
43
+ end
44
+
45
+ def load_user
46
+ @user_path = File.join(@elements_path, 'user')
47
+ Dir.glob(File.join @user_path, '**', '*.rb').select {|p| p =~ /\.rb$/}.each do |u|
48
+ puts u if $debug
49
+ require "#{u}"
50
+ end #each
51
+ end
52
+
53
+ def load_components
54
+ @components_path = File.join(@elements_path, 'components')
55
+ Dir.glob(File.join @components_path, '**', '*.rb').select {|p| p =~ /\.rb$/}.each do |c|
56
+ puts c if $debug
57
+ require "#{c}"
58
+ end #each
59
+ end
60
+
61
+ def load_pages
62
+ @pages_path = File.join(@elements_path, 'pages')
63
+ Dir.glob(File.join @pages_path, '**', '*.rb').select { |p| p =~ /\.rb$/ }.each do |page|
64
+ puts "#{page}"if $debug
65
+ require "#{page}"
66
+ end #each
67
+ end
68
+
69
+ end
70
+ end
@@ -0,0 +1,323 @@
1
+ require 'optparse'
2
+ require 'tempfile'
3
+ require 'actir'
4
+
5
+ module Actir
6
+ module ParallelTests
7
+
8
+ class CLI
9
+ def run(argv)
10
+ $env = "online"
11
+ options = parse_options!(argv)
12
+ num_processes = Actir::ParallelTests.determine_number_of_processes(options[:count])
13
+ $mode = Actir::ParallelTests.determine_run_env(options[:mode])
14
+ if options[:execute]
15
+ execute_shell_command_in_parallel(options[:execute], num_processes, options)
16
+ else
17
+ run_tests_in_parallel(num_processes, options)
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def execute_in_parallel(items, num_processes, options)
24
+ Tempfile.open 'parallel_tests-lock' do |lock|
25
+ return Parallel.map(items, :in_threads => num_processes) do |item|
26
+ result = yield(item)
27
+ report_output(result, lock) if options[:serialize_stdout]
28
+ result
29
+ end
30
+ end
31
+ end
32
+
33
+ def run_tests_in_parallel(num_processes, options)
34
+ test_results = nil
35
+
36
+ #修改全局变量$env至对应的预发布环境的名字
37
+ if options[:pre_name]
38
+ # 不等于当前的预发环境
39
+ if options[:pre_name] != $env
40
+ $env = options[:pre_name]
41
+ end
42
+ else
43
+ if options[:pre_name] != $env
44
+ $env = "online"
45
+ end
46
+ end
47
+
48
+ report_time_taken do
49
+ groups = @runner.tests_in_groups(options[:files], num_processes, options)
50
+
51
+ # @modify by Hub
52
+ # @Date : 2015.3.9
53
+ # 远程执行模式下获取服务器IP和端口号
54
+ address = []
55
+ if $mode == :remote
56
+ address = Actir::Remote.get_remote_address(num_processes)
57
+ num_processes = address.size
58
+ end
59
+
60
+ #更新百度支付-百付宝的cookies
61
+ if options[:update]
62
+ Actir::CookiesBaidu.update_all
63
+ end
64
+
65
+ #计算重跑次数
66
+ re_run_times = Actir::ParallelTests.determine_times_of_rerun(options[:rerun])
67
+ #报用例数
68
+ report_number_of_tests(groups)
69
+ #报执行环境
70
+ report_address_of_env(address)
71
+ #并发执行不同group中的测试用例
72
+ test_results = execute_in_parallel(groups, groups.size, options) do |group|
73
+ p_num = groups.index(group)
74
+ #执行用例脚本
75
+ result = run_tests(group, p_num, num_processes, options, address[p_num])
76
+ #从结果中取出失败用例重跑
77
+ if ( result[:exit_status] != 0 ) && ( re_run_times > 0 )
78
+ result = Actir::ParallelTests::Test::Rerun.re_run_tests(result, p_num, num_processes, options, address[p_num], re_run_times)
79
+ end
80
+ result
81
+ end
82
+
83
+ #顺序输出并发执行过程
84
+ show_process_serialize(num_processes, options)
85
+
86
+ #输出最终的执行结果
87
+ report_results(test_results)
88
+ end
89
+
90
+ abort final_fail_message if any_test_failed?(test_results)
91
+ end
92
+
93
+ def run_tests(group, process_number, num_processes, options, address)
94
+ if group.empty?
95
+ {:stdout => '', :exit_status => 0}
96
+ else
97
+ #puts pre_str + "ready to exec #{group}"
98
+ @runner.run_tests(group, process_number, num_processes, options, address)
99
+ end
100
+ end
101
+
102
+ def report_output(result, lock)
103
+ lock.flock File::LOCK_EX
104
+ $stdout.puts result[:stdout]
105
+ $stdout.flush
106
+ ensure
107
+ lock.flock File::LOCK_UN
108
+ end
109
+
110
+ def report_results(test_results)
111
+ results = @runner.find_results(test_results.map { |result| result[:stdout] }*"")
112
+ puts division_str
113
+ puts pre_str + @runner.summarize_results(results)
114
+ #生成详细报告
115
+ #detail_report(@runner.summarize_results(results),file_path)
116
+ #puts pre_str + any_test_failed?(test_results).to_s
117
+ end
118
+
119
+ def report_number_of_tests(groups)
120
+ name = @runner.test_file_name
121
+ num_processes = groups.size
122
+ num_tests = groups.map(&:size).inject(:+)
123
+ puts division_str
124
+ puts pre_str + "#{num_processes} processes for #{num_tests} #{name}s, ~ #{num_tests / groups.size} #{name}s per process"
125
+ #puts division_str
126
+ end
127
+
128
+ # add by Hub
129
+ # show test env address
130
+ def report_address_of_env(address)
131
+ if $mode == :remote
132
+ node_name = Actir::Config.get("config.test_mode.docker.name")
133
+ address.each_with_index do |ip, i|
134
+ puts " " + $env + node_name + (i+1).to_s + " : " + ip
135
+ end
136
+ else
137
+ puts " " + "local"
138
+ end
139
+ puts division_str
140
+ end
141
+
142
+ #add by Hub
143
+ #show result of every process exec testcases
144
+ #this func will last for a while due to the big logfile
145
+ def show_process_serialize(num_processes, options)
146
+ if options[:log]
147
+ puts "\n" + division_str + pre_str + "SHOW_PROCESS_LOG--START\n" + division_str
148
+ for i in 0..(num_processes-1)
149
+ Actir::ParallelTests::Test::Logger.show_log(i)
150
+ puts division_str
151
+ end
152
+ puts division_str + pre_str + "SHOW_PROCESS_LOG--END\n" + division_str
153
+ end
154
+ end
155
+
156
+ #exit with correct status code so rake parallel:test && echo 123 works
157
+ def any_test_failed?(test_results)
158
+ test_results.any? { |result| result[:exit_status] != 0 }
159
+ end
160
+
161
+ def parse_options!(argv)
162
+ options = {}
163
+ @runner = load_runner("test")
164
+ OptionParser.new do |opts|
165
+ opts.banner = <<-BANNER.gsub(/^ /, '')
166
+ Run all tests in parallel
167
+ Usage: ruby [switches] [--] [files & folders]
168
+ Options are:
169
+ BANNER
170
+ opts.on("-n [PROCESSES]", Integer, "How many processes to use, default: 1") { |n| options[:count] = n }
171
+ opts.on("--group-by [TYPE]", <<-TEXT.gsub(/^ /, '')
172
+ group tests by:
173
+ found - order of finding files
174
+ filesize - by size of the file
175
+ default - filesize
176
+ TEXT
177
+ ) { |type| options[:group_by] = type.to_sym }
178
+ opts.on("-r [TIMES]", "--rerun [TIMES]", Integer, "rerun times for failure&error testcase, default: 0") { |n| options[:rerun] = n }
179
+ #opts.on("-m [FLOAT]", "--multiply-processes [FLOAT]", Float, "use given number as a multiplier of processes to run") { |multiply| options[:multiply] = multiply }
180
+ opts.on("-i", "--isolate",
181
+ "Do not run any other tests in the group used by --single(-s)") do |pattern|
182
+ options[:isolate] = true
183
+ end
184
+ opts.on("-e", "--exec [COMMAND]", "execute this code parallel") { |path| options[:execute] = path }
185
+ opts.on("--serialize-stdout", "Serialize stdout output, nothing will be written until everything is done") { options[:serialize_stdout] = true }
186
+ opts.on("--combine-stderr", "Combine stderr into stdout, useful in conjunction with --serialize-stdout") { options[:combine_stderr] = true }
187
+ opts.on("--non-parallel", "execute same commands but do not in parallel, needs --exec") { options[:non_parallel] = true }
188
+ opts.on("--nice", "execute test commands with low priority") { options[:nice] = true }
189
+ opts.on("--verbose", "Print more output") { options[:verbose] = true }
190
+ opts.on("--log", "record exec result to logfile") { options[:log] = true}
191
+ opts.on("--remote", "run testcase in remote environment") { options[:mode] = :remote }
192
+ opts.on("--local", "run testcase in local environment") { options[:mode] = :local }
193
+ # 填写预发环境,目前只支持bjpre2-4,别的后续再添加
194
+ opts.on("-p", "--pre [PRE]", <<-TEXT.gsub(/^ /, '')
195
+ set pre environment to run testcase:
196
+ bjpre2
197
+ bjpre3
198
+ bjpre4
199
+ TEXT
200
+ ) { |pre| pre = "online" if ( pre != "bjpre2" && pre != "bjpre3" && pre != "bjpre4"); options[:pre_name] = pre }
201
+ #add by Hub
202
+ #-u commnd, update baifubao's cookies
203
+ opts.on("-u", "--update", "Update Baifubao's cookies") { options[:update] = true }
204
+ #add by Hub
205
+ #-s commnd, show test mode,and remote env ipaddress
206
+ opts.on("-s", "--show [PATH]", "Show Test Mode") do |path|
207
+ abort "Please input project directory path!" if path == nil
208
+ $project_path = File.join(Dir.pwd, path)
209
+ puts division_str
210
+ if Actir::Config.get("config.test_mode.env") == :local
211
+ puts "mode : Local"
212
+ else
213
+ puts "mode : Remote"
214
+ node_name = Actir::Config.get("config.test_mode.docker.name")
215
+ address = Actir::Remote.get_remote_address
216
+ puts "node_num : " + address.size.to_s
217
+ address.each_with_index do |address, i|
218
+ puts $env + node_name + (i+1).to_s + " : " + address
219
+ end
220
+ end
221
+ puts division_str
222
+ exit
223
+ end
224
+ opts.on("-h", "--help", "Show this.") { puts opts; exit }
225
+ end.parse!(argv)
226
+
227
+ if options[:count] == 0
228
+ options.delete(:count)
229
+ options[:non_parallel] = true
230
+ end
231
+
232
+ abort "Pass files or folders to run" if argv.empty? && !options[:execute]
233
+
234
+ #如果argv为空,则默认执行所有测试文件
235
+ #遍历testcode下所有文件,组成字符串
236
+ options[:files] = argv
237
+ get_project_path(argv)
238
+
239
+ options
240
+ end
241
+
242
+ #根据传入的测试文件/文件夹的路径,获取测试工程的路径
243
+ def get_project_path(argv)
244
+ testcode_path = ""
245
+ (argv || []).map do |file_or_folder|
246
+ test_path = File.join(Dir.pwd , file_or_folder)
247
+ #获取testcode文件夹的path
248
+ if test_path =~ /(\/.*\/testcode)/
249
+ testcode_path = $1
250
+ break
251
+ end
252
+ end
253
+ #根据testcode path 拿到project的path
254
+ #要求testcode必须是project的下一级/testcode必须是config的平级
255
+ $project_path = File.join(testcode_path, "../")
256
+ end
257
+
258
+ def load_runner(type)
259
+ require "actir/parallel_tests/#{type}/runner"
260
+ require "actir/parallel_tests/#{type}/re_run"
261
+ require "actir/parallel_tests/test/logger"
262
+ runner_classname = type.split("_").map(&:capitalize).join.sub("Rspec", "RSpec")
263
+ klass_name = "Actir::ParallelTests::#{runner_classname}::Runner"
264
+ klass_name.split('::').inject(Object) { |x, y| x.const_get(y) }
265
+ end
266
+
267
+ def execute_shell_command_in_parallel(command, num_processes, options)
268
+ runs = (0...num_processes).to_a
269
+ results = if options[:non_parallel]
270
+ runs.map do |i|
271
+ Actir::ParallelTests::Test::Runner.execute_command(command, i, num_processes, options)
272
+ end
273
+ else
274
+ execute_in_parallel(runs, num_processes, options) do |i|
275
+ Actir::ParallelTests::Test::Runner.execute_command(command, i, num_processes, options)
276
+ end
277
+ end.flatten
278
+
279
+ abort if results.any? { |r| r[:exit_status] != 0 }
280
+ end
281
+
282
+ def report_time_taken
283
+ seconds = Actir::ParallelTests.delta { yield }.to_i
284
+ puts "\n" + pre_str + "Cost #{seconds} seconds#{detailed_duration(seconds)}\n"
285
+ puts division_str
286
+ end
287
+
288
+ def detailed_duration(seconds)
289
+ parts = [ seconds / 3600, seconds % 3600 / 60, seconds % 60 ].drop_while(&:zero?)
290
+ return if parts.size < 2
291
+ parts = parts.map { |i| "%02d" % i }.join(':').sub(/^0/, '')
292
+ " (#{parts})"
293
+ end
294
+
295
+ def final_fail_message
296
+ fail_message = "#{@runner.name}s Failed\n"
297
+ fail_message = "\e[31m#{fail_message}\e[0m" if use_colors?
298
+
299
+ division_str + pre_str + fail_message + division_str
300
+ end
301
+
302
+ def use_colors?
303
+ $stdout.tty?
304
+ end
305
+
306
+ def pre_str
307
+ " Actir : "
308
+ end
309
+
310
+ # add by Hub
311
+ # division for Actir report
312
+ def division_str
313
+ "---------------------------------------------------------------------------------------------\n"
314
+ end
315
+
316
+ # def detail_report(output)
317
+
318
+ # HtmlPrinter.new(file)
319
+ # end
320
+
321
+ end
322
+ end
323
+ end
@@ -0,0 +1,44 @@
1
+ module Actir
2
+ module ParallelTests
3
+ class Grouper
4
+ class << self
5
+
6
+ def in_even_groups_by_size(items, num_groups, options= {})
7
+ groups = Array.new(num_groups) { {:items => [], :size => 0} }
8
+
9
+ groups_to_fill = (options[:isolate] ? groups[1..-1] : groups)
10
+ group_features_by_size(items_to_group(items), groups_to_fill)
11
+
12
+ groups.map!{|g| g[:items].sort }
13
+ end
14
+
15
+ private
16
+
17
+ def largest_first(files)
18
+ files.sort_by{|item, size| size }.reverse
19
+ end
20
+
21
+ def smallest_group(groups)
22
+ groups.min_by{|g| g[:size] }
23
+ end
24
+
25
+ def add_to_group(group, item, size)
26
+ group[:items] << item
27
+ group[:size] += size
28
+ end
29
+
30
+ def group_features_by_size(items, groups_to_fill)
31
+ items.each do |item, size|
32
+ size ||= 1
33
+ smallest = smallest_group(groups_to_fill)
34
+ add_to_group(smallest, item, size)
35
+ end
36
+ end
37
+
38
+ def items_to_group(items)
39
+ items.first && items.first.size == 2 ? largest_first(items) : items
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,77 @@
1
+ require "parallel"
2
+ require 'actir/parallel_tests/cli'
3
+ require 'actir/parallel_tests/grouper'
4
+
5
+ module Actir
6
+ module ParallelTests
7
+
8
+ autoload :CLI, "cli"
9
+ autoload :Grouper, "grouper"
10
+
11
+ # 一些通用类方法
12
+ class << self
13
+ #判断执行的进程数
14
+ def determine_number_of_processes(count)
15
+ [
16
+ count,
17
+ #ENV["PARALLEL_TEST_PROCESSORS"],
18
+ #核数
19
+ #Parallel.processor_count
20
+ #count数不填模式为1
21
+ 1
22
+ ].detect{|c| not c.to_s.strip.empty? }.to_i
23
+ end
24
+
25
+ #判断失败用例重新执行的次数
26
+ def determine_times_of_rerun(times)
27
+ [
28
+ times,
29
+ 0
30
+ #Actir::Config.get("config.test_mode.re_run")
31
+ ].detect{|c| not c.to_s.strip.empty? }.to_i
32
+ end
33
+
34
+ #判断用例执行的环境是local还是remote
35
+ def determine_run_env(mode)
36
+ env_mode = :local
37
+ #判断是否存在config.yaml配置文件,如果不存在,则test_mode给默认值
38
+ if File.exist?(File.join($project_path, "config", "config.yaml"))
39
+ #刷新配置文件中的env配置项为remote模式,以防止本地调试代码改写上传后导致CI失败
40
+ if mode
41
+ unless mode == /#{Actir::Config.get("config.test_mode.env")}/
42
+ #同步修改配置文件,需要先将Symbol转换成String
43
+ mode_str = ":" + mode.to_s
44
+ Actir::Config.set("config.test_mode.env", mode_str)
45
+ end
46
+ env_mode = mode
47
+ else
48
+ env_mode = Actir::Config.get("config.test_mode.env")
49
+ end
50
+ else
51
+ if mode
52
+ env_mode = mode
53
+ end
54
+ end
55
+ ENV["mode"] = env_mode.to_s
56
+ env_mode
57
+ end
58
+
59
+ # real time even if someone messed with timecop in tests
60
+ def now
61
+ if Time.respond_to?(:now_without_mock_time) # Timecop
62
+ Time.now_without_mock_time
63
+ else
64
+ Time.now
65
+ end
66
+ end
67
+
68
+ def delta
69
+ before = now.to_f
70
+ yield
71
+ now.to_f - before
72
+ end
73
+
74
+ end
75
+
76
+ end
77
+ end