tes-request 0.1
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.
- 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
|