tes-request 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +3 -0
- data/.travis.yml +22 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +79 -0
- data/Rakefile +6 -0
- data/bin/tes-ci-slicer +26 -0
- data/lib/hash.rb +24 -0
- data/lib/tes/request.rb +3 -0
- data/lib/tes/request/ask.rb +126 -0
- data/lib/tes/request/client.rb +72 -0
- data/lib/tes/request/expression.rb +116 -0
- data/lib/tes/request/profile.rb +282 -0
- data/lib/tes/request/rake_task.rb +90 -0
- data/lib/tes/request/rspec/ci_slicer.rb +82 -0
- data/lib/tes/request/rspec/distribute.rb +214 -0
- data/lib/tes/request/rspec/function.rb +48 -0
- data/lib/tes/request/rspec/profile_parser.rb +132 -0
- data/lib/tes/request/version.rb +5 -0
- data/tes-request.gemspec +40 -0
- metadata +125 -0
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'java-properties'
|
3
|
+
require 'fileutils'
|
4
|
+
require_relative 'distribute'
|
5
|
+
|
6
|
+
module Tes::Request::RSpec
|
7
|
+
class CiSlicer
|
8
|
+
SUPPORT_FILE_TYPES = [:yaml, :yml, :json, :properties]
|
9
|
+
|
10
|
+
def initialize(file_type, project_dir, res_replace_map_json_file=nil)
|
11
|
+
unless SUPPORT_FILE_TYPES.include?(file_type.to_sym)
|
12
|
+
raise(ArgumentError, "Not supported file type:#{file_type}!")
|
13
|
+
end
|
14
|
+
|
15
|
+
@cfg_file_type = file_type
|
16
|
+
@project_dir = project_dir
|
17
|
+
@res_addition_attr_map = (res_replace_map_json_file && JSON.parse(File.read(res_replace_map_json_file)))
|
18
|
+
@cfg_target_dir = File.join(@project_dir, '.ci_jobs')
|
19
|
+
end
|
20
|
+
|
21
|
+
def run(ci_type, slice_count)
|
22
|
+
puts "Generate RSpec distribute jobs #{@cfg_file_type} file for CI"
|
23
|
+
rspec_distribute = Distribute.new(@project_dir)
|
24
|
+
jobs = rspec_distribute.distribute_jobs(ci_type, slice_count, @res_addition_attr_map)
|
25
|
+
save_job_files(jobs, @cfg_target_dir, @cfg_file_type)
|
26
|
+
@cfg_target_dir
|
27
|
+
end
|
28
|
+
|
29
|
+
def spec_tag_param_str(tags)
|
30
|
+
case tags
|
31
|
+
when Array
|
32
|
+
tags.map { |t| "--tag #{t}" }.join(' ')
|
33
|
+
when String
|
34
|
+
"--tag #{tags}"
|
35
|
+
when nil
|
36
|
+
nil
|
37
|
+
else
|
38
|
+
raise("不支持的类型:#{tags.class}")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def save_job_files(jobs, target_dir, file_type)
|
43
|
+
unless SUPPORT_FILE_TYPES.include?(file_type)
|
44
|
+
raise(ArgumentError, "Not supported file type:#{file_type}!")
|
45
|
+
end
|
46
|
+
|
47
|
+
job_configs_for_ci = jobs.map { |j| gen_job_ci_params(j) }
|
48
|
+
FileUtils.rm_rf(target_dir)
|
49
|
+
FileUtils.mkdir(target_dir)
|
50
|
+
case file_type
|
51
|
+
when :json
|
52
|
+
save_file = File.join(target_dir, 'ci_tasks.json')
|
53
|
+
File.open(save_file, 'w') { |f| f.write job_configs_for_ci.to_json }
|
54
|
+
puts "Generated #{jobs.size} jobs, Stored in:#{save_file} ."
|
55
|
+
when :yml, :yaml
|
56
|
+
save_file = File.join(target_dir, 'ci_tasks.yml')
|
57
|
+
File.open(save_file, 'w') { |f| f.write job_configs_for_ci.to_yaml }
|
58
|
+
puts "Generated #{jobs.size} jobs, Stored in:#{save_file} ."
|
59
|
+
when :properties
|
60
|
+
job_configs_for_ci.each_with_index do |params, i|
|
61
|
+
file = File.join(target_dir, "#{i}.properties")
|
62
|
+
JavaProperties.write(params, file)
|
63
|
+
end
|
64
|
+
puts "Generated #{jobs.size} jobs, Stored in:#{target_dir}/*.properties ."
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_job_rspec_run_args_str(job, split=' ')
|
69
|
+
tags_str = spec_tag_param_str(job[:tag])
|
70
|
+
paths_str = job[:specs].join(split)
|
71
|
+
tags_str ? (tags_str + split + paths_str) : paths_str
|
72
|
+
end
|
73
|
+
|
74
|
+
def get_job_env_profile_str(job, split=';')
|
75
|
+
job[:profile].to_s(split)
|
76
|
+
end
|
77
|
+
|
78
|
+
def gen_job_ci_params(job)
|
79
|
+
{'RSPEC_PARAM' => get_job_rspec_run_args_str(job), 'REQUEST_ASKS' => get_job_env_profile_str(job)}
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'json'
|
3
|
+
require_relative 'profile_parser'
|
4
|
+
|
5
|
+
module Tes
|
6
|
+
module Request
|
7
|
+
module RSpec
|
8
|
+
class Distribute
|
9
|
+
include Function
|
10
|
+
|
11
|
+
DEFAULT_CI_YAML_FILE = '.ci.yaml'
|
12
|
+
EXCLUDE_CLUSTER_RES_PATTERN_PROFILE = '.ci_exclude_res_pattern'
|
13
|
+
|
14
|
+
@@ci_yaml_file = DEFAULT_CI_YAML_FILE
|
15
|
+
@@ci_exclude_cluster_pattern_file = EXCLUDE_CLUSTER_RES_PATTERN_PROFILE
|
16
|
+
|
17
|
+
# @param [String] project_dir 测试项目的根目录路径
|
18
|
+
# @param [String] ci_yaml_file 测试项目内的描述spec测试的配置文件路径(相对`project_dir`)
|
19
|
+
def initialize(project_dir, ci_yaml_file=@@ci_yaml_file)
|
20
|
+
@project_dir = project_dir
|
21
|
+
@ci_cfg = YAML.load_file(File.join(@project_dir, ci_yaml_file))
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :project_dir, :ci_cfg
|
25
|
+
|
26
|
+
# 生成分发任务的配置结构
|
27
|
+
# @return [Array<Hash>]
|
28
|
+
def distribute_jobs(type, count, res_addition_attr_map=nil)
|
29
|
+
task_cfg = get_rspec_task(type)
|
30
|
+
spec_paths = spec_files(type)
|
31
|
+
rspec_parser = Tes::Request::RSpec::ProfileParser.new(spec_paths)
|
32
|
+
rspec_parser.parse_profiles!
|
33
|
+
|
34
|
+
gen_pieces(rspec_parser.profiles, count).map do |piece|
|
35
|
+
profile = piece[:profile]
|
36
|
+
if res_addition_attr_map and res_addition_attr_map.size > 0
|
37
|
+
res_addition_attr_map.each do |res_filter_pattern, attr_add_map|
|
38
|
+
request_asks = profile.data.select { |ask| ask.to_s.include? res_filter_pattern }
|
39
|
+
request_asks.each do |ask|
|
40
|
+
# only add the resource attribution when no request the attribution for the resource
|
41
|
+
attr_add_map.each do |attr_name, attr_limit|
|
42
|
+
unless ask.data.include?(attr_name)
|
43
|
+
ask.data[attr_name] = Tes::Request::Expression.new("#{attr_name}#{attr_limit}")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
specs = piece[:specs].inject([]) do |t, spec|
|
51
|
+
file_path = spec[:file].sub(/^#{@project_dir}/, '').sub(/^\//, '')
|
52
|
+
if (spec[:locations] and spec[:locations].size > 0) or (spec[:ids] and spec[:ids].size > 0)
|
53
|
+
if spec[:locations] and spec[:locations].size > 0
|
54
|
+
t.push(file_path + ':' + spec[:locations].join(':'))
|
55
|
+
end
|
56
|
+
if spec[:ids] and spec[:ids].size > 0
|
57
|
+
t.push(file_path + '[' + spec[:ids].join(',') + ']')
|
58
|
+
end
|
59
|
+
else
|
60
|
+
t.push file_path
|
61
|
+
end
|
62
|
+
t
|
63
|
+
end
|
64
|
+
|
65
|
+
{profile: profile, specs: specs}
|
66
|
+
end.map { |p| p.merge(tag: task_cfg['tag']) }
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
# 生产任务碎片,尽量接近传递的参数值
|
71
|
+
# @param [Fixnum] minimum_pieces
|
72
|
+
# @return [Array]
|
73
|
+
def gen_pieces(profiles, minimum_pieces)
|
74
|
+
ret = []
|
75
|
+
min_spec_count = profiles.size / minimum_pieces
|
76
|
+
|
77
|
+
profiles.each do |to_merge_spec|
|
78
|
+
# 0. 任务发布要求的特殊处理
|
79
|
+
if to_merge_spec[:distribute] && to_merge_spec[:distribute][:standalone]
|
80
|
+
ret << {profile: to_merge_spec[:profile], specs: [to_merge_spec]}
|
81
|
+
next
|
82
|
+
end
|
83
|
+
|
84
|
+
# 1. 优先相同要求的归并
|
85
|
+
join_piece = ret.find do |piece|
|
86
|
+
piece[:specs].size <= min_spec_count and
|
87
|
+
piece[:profile] == to_merge_spec[:profile]
|
88
|
+
end
|
89
|
+
if join_piece
|
90
|
+
# puts 'http://join'
|
91
|
+
join_piece[:specs] << to_merge_spec
|
92
|
+
else
|
93
|
+
# 2. 然后再是资源多少不同的归并
|
94
|
+
super_piece = ret.find do |piece|
|
95
|
+
if piece[:specs].size <= min_spec_count
|
96
|
+
cr = piece[:profile] <=> to_merge_spec[:profile]
|
97
|
+
cr && cr >= 0
|
98
|
+
else
|
99
|
+
false
|
100
|
+
end
|
101
|
+
end
|
102
|
+
if super_piece
|
103
|
+
# puts 'http://inherit'
|
104
|
+
super_piece[:specs] << to_merge_spec
|
105
|
+
else
|
106
|
+
# 3. 可整合计算的的归并,但要求已经达到的任务分片数已经达到了要求那么大,否则直接以新建来搞
|
107
|
+
if ret.size >= minimum_pieces
|
108
|
+
merge_piece = ret.find do |piece|
|
109
|
+
piece[:specs].size <= min_spec_count and
|
110
|
+
piece[:profile].merge_able?(to_merge_spec[:profile])
|
111
|
+
end
|
112
|
+
if merge_piece
|
113
|
+
# puts 'http://merge'
|
114
|
+
merge_piece[:profile] = merge_piece[:profile] + to_merge_spec[:profile]
|
115
|
+
# puts merge_piece[:profile]
|
116
|
+
merge_piece[:specs] << to_merge_spec
|
117
|
+
else
|
118
|
+
# 4. 最后再尝试独立出一个新的piece,在剩余数量达到一半要求的时候
|
119
|
+
# puts 'http://new'
|
120
|
+
ret << {profile: to_merge_spec[:profile], specs: [to_merge_spec]}
|
121
|
+
end
|
122
|
+
else
|
123
|
+
ret << {profile: to_merge_spec[:profile], specs: [to_merge_spec]}
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
ret
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
# @param [String] type
|
133
|
+
# @return [Hash] rspec ci task info
|
134
|
+
def get_rspec_task(type)
|
135
|
+
raise("No CI Task:#{type}") unless @ci_cfg.key?(type)
|
136
|
+
@ci_cfg[type]
|
137
|
+
end
|
138
|
+
|
139
|
+
# @return [Array<String>]
|
140
|
+
def spec_files(task_type)
|
141
|
+
spec_cfg = get_rspec_task(task_type)['spec']
|
142
|
+
spec_paths = filter_spec_by_path(spec_cfg['pattern'], spec_cfg['exclude_pattern'])
|
143
|
+
|
144
|
+
exclude_cluster_profile_file = File.join(@project_dir, @@ci_exclude_cluster_pattern_file)
|
145
|
+
if File.exists?(exclude_cluster_profile_file)
|
146
|
+
exclude_patterns = File.readlines(exclude_cluster_profile_file).map(&:strip)
|
147
|
+
exclude_spec_by_resource(spec_paths, exclude_patterns)
|
148
|
+
else
|
149
|
+
spec_paths
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# @return [Array<String>]
|
154
|
+
def filter_spec_by_path(pattern, exclude_pattern=nil)
|
155
|
+
pattern_filter_lab = ->(p) do
|
156
|
+
spec_info = get_spec_path_info(p)
|
157
|
+
direct_return = (spec_info[:locations] or spec_info[:ids])
|
158
|
+
direct_return ? [File.join(@project_dir, p)] : Dir[File.join(@project_dir, p)]
|
159
|
+
end
|
160
|
+
|
161
|
+
ret = case pattern
|
162
|
+
when String
|
163
|
+
pattern_filter_lab.call(pattern)
|
164
|
+
Dir[File.join(@project_dir, pattern)]
|
165
|
+
when Array
|
166
|
+
pattern.inject([]) { |t, ep| t + pattern_filter_lab.call(ep) }
|
167
|
+
else
|
168
|
+
raise('Error pattern type')
|
169
|
+
end
|
170
|
+
|
171
|
+
return ret unless exclude_pattern
|
172
|
+
|
173
|
+
case exclude_pattern
|
174
|
+
when String
|
175
|
+
ret -= Dir[File.join(@project_dir, exclude_pattern)]
|
176
|
+
when Array
|
177
|
+
ret -= exclude_pattern.inject([]) { |t, ep| t + Dir[File.join(@project_dir, ep)] }
|
178
|
+
else
|
179
|
+
raise('Error exclude_pattern type')
|
180
|
+
end
|
181
|
+
|
182
|
+
ret
|
183
|
+
end
|
184
|
+
|
185
|
+
# 按照指定资源属性排除要求排除相应的spec路径
|
186
|
+
# @param [Array<String>] spec_paths spec的执行路径列表
|
187
|
+
# @param [Array<String>] res_exclude_patterns,
|
188
|
+
# 每一个元素的格式是这样:
|
189
|
+
# res_type: res_attr1=2,res_attr3>=4
|
190
|
+
# 或者
|
191
|
+
# type=res_type,res_attr1=2,res_attr3>=4
|
192
|
+
# @return [Array<String>] 按照`res_exclude_pattern` 剔除后的 `spec_paths`
|
193
|
+
def exclude_spec_by_resource(spec_paths, res_exclude_patterns=[])
|
194
|
+
return spec_paths if res_exclude_patterns.empty?
|
195
|
+
|
196
|
+
spec_paths.reject do |spec_path|
|
197
|
+
spec_info = get_spec_path_info(spec_path)
|
198
|
+
profile_lines = parse_spec_profile_lines(spec_info[:file])
|
199
|
+
res_exclude_patterns.any? do |exclude_pattern|
|
200
|
+
res_attrs = if exclude_pattern =~ /^\w+:\s*.+/
|
201
|
+
type, attrs = exclude_pattern.split(/:\s*/, 2)
|
202
|
+
"type=#{type}," + attrs
|
203
|
+
else
|
204
|
+
exclude_pattern
|
205
|
+
end
|
206
|
+
res_attrs = res_attrs.split(',')
|
207
|
+
profile_lines.any? { |line| res_attrs.all? { |attr| line =~ /\b#{attr}\b/ } }
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Tes
|
2
|
+
module Request
|
3
|
+
module RSpec
|
4
|
+
module Function
|
5
|
+
# 对spec路径获取相关信息(行号或者序号等信息)
|
6
|
+
# @param [String] spec_path
|
7
|
+
# @param [Hash]
|
8
|
+
def get_spec_path_info(spec_path)
|
9
|
+
ids_reg = /\[([\d:,]+)\]$/
|
10
|
+
locations_reg = /:([\d:]+)$/
|
11
|
+
if spec_path =~ ids_reg
|
12
|
+
ids = spec_path.match(ids_reg)[1].split(',')
|
13
|
+
file_path = spec_path.sub(ids_reg, '')
|
14
|
+
{file: file_path, ids: ids}
|
15
|
+
elsif spec_path =~ locations_reg
|
16
|
+
locations = spec_path.match(locations_reg)[1].split(':').map(&:to_i)
|
17
|
+
file_path = spec_path.sub(locations_reg, '')
|
18
|
+
{file: file_path, locations: locations}
|
19
|
+
else
|
20
|
+
{file: spec_path}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# 解析spec文件的测试环境要求文本内容
|
25
|
+
# @return [Array<String>]
|
26
|
+
def parse_spec_profile_lines(spec_file)
|
27
|
+
ret = nil
|
28
|
+
File.open(spec_file, 'r') do |f|
|
29
|
+
f.each_line do |l|
|
30
|
+
case l
|
31
|
+
when /^\s*#\s*@env\s+begin\s*$/
|
32
|
+
ret = []
|
33
|
+
when /^\s*#\s*@end/
|
34
|
+
break
|
35
|
+
when /^\s*#/
|
36
|
+
ret << l.sub(/^\s*#\s*/, '') if ret
|
37
|
+
else
|
38
|
+
#nothing
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
ret && ret.map(&:strip).map { |l| l.split(/\s*;\s*/) }.flatten
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require_relative '../profile'
|
2
|
+
require_relative 'function'
|
3
|
+
|
4
|
+
# 分析rspec profile信息
|
5
|
+
module Tes
|
6
|
+
module Request
|
7
|
+
module RSpec
|
8
|
+
class ProfileParser
|
9
|
+
def initialize(spec_paths=[])
|
10
|
+
@spec_paths = spec_paths
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :profiles
|
14
|
+
|
15
|
+
def <<(spec_path)
|
16
|
+
@spec_paths << spec_path
|
17
|
+
end
|
18
|
+
|
19
|
+
def parse_profiles!
|
20
|
+
profile_map = {}
|
21
|
+
|
22
|
+
@spec_paths.each do |s_p|
|
23
|
+
spec_info = parse_spec(s_p)
|
24
|
+
|
25
|
+
# 如果相同文件则进行合并
|
26
|
+
if profile_map.key?(spec_info[:file])
|
27
|
+
profile_map[spec_info[:file]] = merge_spec_info(profile_map[spec_info[:file]], spec_info)
|
28
|
+
else
|
29
|
+
profile_map[spec_info[:file]] = spec_info
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# 对存在的包含关系的ids尝试合并
|
34
|
+
profile_map.each do |_f, info|
|
35
|
+
info[:ids] = merge_spec_ids(info[:ids]) if info[:ids]
|
36
|
+
info[:locations].sort! if info[:locations]
|
37
|
+
end
|
38
|
+
@profiles = profile_map.values
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
include Function
|
44
|
+
|
45
|
+
# 合并2个spec info数据结构
|
46
|
+
# @param [Hash] a
|
47
|
+
# @param [Hash] b
|
48
|
+
# @return [Hash] merge result, same struct with `a` or `b`
|
49
|
+
def merge_spec_info(a, b)
|
50
|
+
raise('不支持合并不同spec文件的信息') unless a[:file] == b[:file]
|
51
|
+
|
52
|
+
compare_keys = [:ids, :locations]
|
53
|
+
a_compare_keys = a.keys.select { |k| compare_keys.include?(k) }.sort
|
54
|
+
b_compare_keys = b.keys.select { |k| compare_keys.include?(k) }.sort
|
55
|
+
|
56
|
+
case [a_compare_keys, b_compare_keys]
|
57
|
+
# 都有ids的情况
|
58
|
+
when [[:ids], [:ids]], [[:ids, :locations], [:ids]]
|
59
|
+
a.merge(ids: (a[:ids] + b[:ids]).uniq)
|
60
|
+
when [[:ids], [:ids, :locations]]
|
61
|
+
b.merge(ids: (a[:ids] + b[:ids]).uniq)
|
62
|
+
|
63
|
+
# 都有locations的情况
|
64
|
+
when [[:locations], [:locations]], [[:ids, :locations], [:locations]]
|
65
|
+
a.merge(locations: (a[:locations] + b[:locations]).uniq)
|
66
|
+
when [[:locations], [:ids, :locations]]
|
67
|
+
a.merge(locations: (b[:locations] + a[:locations]).uniq)
|
68
|
+
|
69
|
+
# 都有ids和locations
|
70
|
+
when [[:ids, :locations], [:ids, :locations]]
|
71
|
+
a.merge(
|
72
|
+
ids: (a[:ids] + b[:ids]).uniq,
|
73
|
+
locations: (a[:locations] + b[:locations]).uniq
|
74
|
+
)
|
75
|
+
|
76
|
+
# 互补
|
77
|
+
when [[:ids], [:locations]], [[:locations], [:ids]]
|
78
|
+
a.merge b
|
79
|
+
else
|
80
|
+
# 只剩下 a_compare_keys 为空 或者 b_compare_keys为空的情况
|
81
|
+
a_compare_keys.empty? ? a : b
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# @param [Array<String>] ids
|
86
|
+
def merge_spec_ids(ids)
|
87
|
+
ids.sort.inject([]) do |t, id|
|
88
|
+
id_is_covered = (t.last && id.index(t.last) == 0)
|
89
|
+
id_is_covered ? t : t.push(id)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# @param [Array<String>] str_array, 每个元素的格式可以是这样:
|
94
|
+
# - `--xxx`
|
95
|
+
# - `-yy`
|
96
|
+
# - `-x vvv`
|
97
|
+
# - `--zz vvv`
|
98
|
+
# @return [Hash<Symbol, Object>]
|
99
|
+
def parse_distribute_profile(str_array)
|
100
|
+
str_array.inject({}) do |d_p, str|
|
101
|
+
args = str.scan(/-+[^-]+\b/).map { |arg| arg.sub(/^-+/, '').split(/\s+/) }
|
102
|
+
d_p_phrase = args.inject({}) { |h, arg| h.merge(arg[0].to_sym => arg[1] || true) }
|
103
|
+
d_p.merge d_p_phrase
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# 解析指定路径spec的执行解析
|
108
|
+
# @param [String] spec_path 当前支持3种格式
|
109
|
+
# - 一种是普通的文件路径格式
|
110
|
+
# - 一种是文件格式基础上增加行数指定的格式,单个或者多个,单个格式 `:123`,多个直接拼接起来
|
111
|
+
# - 一种是文件格式基础上增加用例层次路径的格式,单个或者多个,单个格式`[1:2:3]`,多个在括号内用逗号间隔:`[1:2:3,2:3:4]`
|
112
|
+
# @return [Hash] include keys: `:file`, `:profile`, `:distribute`(optional)
|
113
|
+
def parse_spec(spec_path)
|
114
|
+
spec_info = get_spec_path_info spec_path
|
115
|
+
|
116
|
+
profile_lines = parse_spec_profile_lines(spec_info[:file])
|
117
|
+
raise("#{spec_info[:file]} 没有ci profile配置,请确认!") unless profile_lines and profile_lines.size > 0
|
118
|
+
|
119
|
+
distribute_lines = profile_lines.
|
120
|
+
select { |l| l =~ /^\s*@distribute\s+/ }.
|
121
|
+
map { |l| l.sub(/^\s*@distribute\s+/, '') }
|
122
|
+
profile_lines.reject! { |l| l =~ /^\s*@distribute\s+/ }
|
123
|
+
|
124
|
+
spec_info.merge(
|
125
|
+
profile: Profile.new(profile_lines),
|
126
|
+
distribute: parse_distribute_profile(distribute_lines)
|
127
|
+
)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|