tes-request 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,116 @@
1
+ module Tes
2
+ module Request
3
+ class Expression
4
+ include Comparable
5
+
6
+ REG_EXP_CHAIN = /^(!)?([0-9A-Za-z_.]+)(\?|=|>=|<=|<|>)?(-?[\w]+(\.[\d]+)?)?/
7
+
8
+ # @param [String] exp_str 表达式字符串
9
+ def initialize(exp_str)
10
+ mt = exp_str.match(REG_EXP_CHAIN)
11
+ raise(ArgumentError, "非法的表达式片段:\t#{exp_str}") unless mt
12
+
13
+ @data = {}
14
+ @data[:revert] = true if mt[1]
15
+ @data[:left_exp] = mt[2]
16
+ if mt[3]
17
+ @data[:op] = mt[3]
18
+ if mt[4]
19
+ @data[:expect_val] = mt[4]
20
+ if @data[:expect_val] =~ /^-?\d+(\.[\d]+)?/
21
+ @data[:expect_val] = mt[5] ? @data[:expect_val].to_f : @data[:expect_val].to_i
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ attr_reader :data
28
+
29
+ # 计算数据是否满足表达式(深度解析后)
30
+ # @param [Object] data
31
+ # @return [true,false]
32
+ def match?(data)
33
+ op = @data[:op]
34
+ unless data.respond_to?(:get_by_chain)
35
+ raise(ArgumentError, 'data arg should be respond to :get_by_chain')
36
+ else
37
+ ret = if !op
38
+ data.get_by_chain @data[:left_exp]
39
+ elsif op == '?'
40
+ chain_data = data.get_by_chain(@data[:left_exp])
41
+ case chain_data
42
+ when 'off', 'down', 'disable', '0', '', 0
43
+ false
44
+ else
45
+ chain_data
46
+ end
47
+ else
48
+ op = '==' if op == '='
49
+ expect_val = @data[:expect_val]
50
+ data.get_by_chain(@data[:left_exp]).send(op, expect_val)
51
+ end
52
+ @data[:revert] ? !ret : ret
53
+ end
54
+ end
55
+
56
+ def <=>(other)
57
+ return 0 if @data == other.data
58
+ return nil if @data[:revert] != other.data[:revert] or @data[:left_exp] != other.data[:left_exp]
59
+
60
+ if @data[:op] == other.data[:op]
61
+ case @data[:op]
62
+ when '=', '=='
63
+ (@data[:expect_val] == other.data[:expect_val]) ? 0 : nil
64
+ when '>', '>='
65
+ @data[:expect_val] <=> other.data[:expect_val]
66
+ when '<', '<='
67
+ other.data[:expect_val] <=> @data[:expect_val]
68
+ when '?', nil
69
+ 0
70
+ else
71
+ raise("内部错误:出现了不支持的表达式操作符号:#{@data[:op]}")
72
+ end
73
+ else
74
+ case [@data[:op], other.data[:op]]
75
+ when %w(< <=)
76
+ ret = other.data[:expect_val] <=> @data[:expect_val]
77
+ ret == 0 ? 1 : ret
78
+ when %w(< =)
79
+ @data[:expect_val] > other.data[:expect_val] ? -1 : nil
80
+ when %w(= >)
81
+ @data[:expect_val] > other.data[:expect_val] ? 1 : nil
82
+ when %w(= >=)
83
+ @data[:expect_val] >= other.data[:expect_val] ? 1 : nil
84
+ when %w(= <)
85
+ @data[:expect_val] < other.data[:expect_val] ? 1 : nil
86
+ when %w(= <=)
87
+ @data[:expect_val] <= other.data[:expect_val] ? 1 : nil
88
+ when %w(> >=)
89
+ ret = @data[:expect_val] <=> other.data[:expect_val]
90
+ ret == 0 ? 1 : ret
91
+ when %w(> =)
92
+ @data[:expect_val] < other.data[:expect_val] ? -1 : nil
93
+ when %w(>= =)
94
+ @data[:expect_val] <= other.data[:expect_val] ? -1 : nil
95
+ when %w(>= >)
96
+ ret = @data[:expect_val] <=> other.data[:expect_val]
97
+ ret == 0 ? -1 : ret
98
+ when %w(<= =)
99
+ @data[:expect_val] <= other.data[:expect_val] ? 1 : nil
100
+ when %w(<= <)
101
+ ret = other.data[:expect_val] <=> @data[:expect_val]
102
+ ret == 0 ? -1 : ret
103
+ else
104
+ nil
105
+ end
106
+ end
107
+ end
108
+
109
+ def to_s
110
+ ret = [:left_exp, :op, :expect_val].map { |k| @data[k] }.join
111
+ ret = '!' + ret if @data[:revert]
112
+ ret
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,282 @@
1
+ require_relative 'ask'
2
+
3
+ module Tes
4
+ module Request
5
+ class Profile
6
+ include Comparable
7
+
8
+ REG_POINT_ASK = /^\*(\d+):/
9
+ REG_REFER_ASK = /^&(\d+)\./
10
+ REG_POINT_ASK_GREEDY = /^\[\*(\d+)\]:/
11
+ REG_REFER_ASK_GREEDY = /^\[&(\d+)\]\./
12
+
13
+ # 深度解析环境要求
14
+ # @param [Array<String>] profile_lines 测试环境要求语句列表
15
+ def initialize(profile_lines=[])
16
+ @data = []
17
+
18
+ # 不因申明顺序不一致性等其他差异而误判环境要求的一致性
19
+ point_asks = {}
20
+ profile_lines.each do |line|
21
+ case line
22
+ when REG_POINT_ASK
23
+ mt = line.match(REG_POINT_ASK)
24
+ ask = Ask.new line.sub(REG_POINT_ASK, '')
25
+ point_asks[mt[1]] = ask
26
+ when REG_POINT_ASK_GREEDY
27
+ mt = line.match(REG_POINT_ASK_GREEDY)
28
+ ask = Ask.new line.sub(REG_POINT_ASK_GREEDY, '')
29
+ ask.greedy= true
30
+ point_asks[mt[1]] = ask
31
+ when REG_REFER_ASK
32
+ mt= line.match(REG_REFER_ASK)
33
+ src_ask = point_asks[mt[1]]
34
+ ref_exp = Expression.new line.sub(REG_REFER_ASK, '')
35
+ src_ask.reference << ref_exp
36
+ when REG_REFER_ASK_GREEDY
37
+ mt= line.match(REG_REFER_ASK_GREEDY)
38
+ src_ask = point_asks[mt[1]]
39
+ ref_exp = Expression.new line.sub(REG_REFER_ASK_GREEDY, '')
40
+ src_ask.reference << ref_exp
41
+ else
42
+ @data << Ask.new(line)
43
+ end
44
+ end
45
+
46
+ @data += point_asks.values
47
+ end
48
+
49
+ attr_reader :data
50
+
51
+ def <=>(other)
52
+ all_self_hash = self.data.group_by { |e| e.to_s }
53
+ all_self_hash_keys = Set.new all_self_hash.keys
54
+ all_other_hash = other.data.group_by { |e| e.to_s }
55
+ all_other_hash_keys = Set.new all_other_hash.keys
56
+
57
+ # 如果相等或者可比较则直接返回(只在相等的时候有效)
58
+ return 0 if all_self_hash == all_other_hash
59
+
60
+ hash1 = Hash[all_self_hash_keys.to_a.map { |e| [e, true] }]
61
+ all_other_hash_keys.to_a.each do |k|
62
+ hash1.include?(k)
63
+ end
64
+
65
+ if all_self_hash_keys == all_other_hash_keys
66
+ compare_when_keys_same(all_self_hash, all_other_hash)
67
+ elsif all_self_hash_keys < all_other_hash_keys
68
+ compare_when_keys_subset(all_self_hash, all_other_hash)
69
+ elsif all_self_hash_keys > all_other_hash_keys
70
+ ret = compare_when_keys_subset(all_other_hash, all_self_hash)
71
+ ret ? 0 - ret : ret
72
+ else
73
+ nil
74
+ end
75
+ end
76
+
77
+
78
+ def +(other)
79
+ ret = self.<=>(other)
80
+ case ret
81
+ when 0, 1
82
+ self
83
+ when -1
84
+ other
85
+ else
86
+ merge(other)
87
+ end
88
+ end
89
+
90
+ def merge_able?(other)
91
+ self.+(other)
92
+ true
93
+ rescue RuntimeError
94
+ false
95
+ end
96
+
97
+ # 向资源池环境中申请资源,但并不进行锁定,只是有礼貌的进行问询式申请
98
+ # 真需要使用资源,需要在其返回列表后向服务器申请锁定这些资源列表
99
+ # @param [Hash<String,Hash>] pool 所有空闲可用的资源池
100
+ def request(pool)
101
+ get_candidates_lab = ->(ask, answer_cache) do
102
+ pool.keys.select { |k| !answer_cache.include?(k) && ask.match?(pool[k]) }
103
+ end
104
+
105
+ answers_flat = []
106
+ answers = @data.map do |ask|
107
+ candidates = get_candidates_lab.call(ask, answers_flat)
108
+ unless candidates.size > 0
109
+ nil
110
+ else
111
+ if ask.greedy
112
+ answers_flat += candidates
113
+ unless ask.reference.size > 0
114
+ candidates
115
+ else
116
+ candidates.map do |candidate|
117
+ refs = ask.reference.inject([]) do |t, r|
118
+ ret = pool[candidate].get_by_chain(r.data[:left_exp])
119
+ ret.is_a?(Array) ? (t + ret) : (t << ret)
120
+ end
121
+ answers_flat += refs
122
+ [candidate, refs]
123
+ end
124
+ end
125
+ else
126
+ candidate = candidates.first
127
+ answers_flat << candidate
128
+ unless ask.reference.size > 0
129
+ candidate
130
+ else
131
+ refs = ask.reference.inject([]) do |t, r|
132
+ ret = pool[candidate].get_by_chain(r.data[:left_exp])
133
+ ret.is_a?(Array) ? (t + ret) : (t << ret)
134
+ end
135
+ answers_flat += refs
136
+ [candidate, refs]
137
+ end
138
+ end
139
+ end
140
+ end
141
+
142
+ answers
143
+ end
144
+
145
+ def to_s(split="\n")
146
+ ret = []
147
+ point = 0
148
+ @data.each do |ask|
149
+ if ask.reference and ask.reference.size > 0
150
+ point_ask = ask
151
+ refer_asks = ask.reference
152
+ point += 1
153
+ ret << ("*#{point}:" + point_ask.to_s)
154
+ refer_asks.each do |r_ask|
155
+ ret << ("&#{point}." + r_ask.to_s)
156
+ end
157
+ else
158
+ ret << ask.to_s
159
+ end
160
+ end
161
+ ret.join(split)
162
+ end
163
+
164
+ private
165
+ def compare_when_keys_same(hash_self, hash_other)
166
+ size_compare_results = hash_self.keys.map { |k| hash_self[k].size <=> hash_other[k].size }
167
+ if size_compare_results.all? { |v| v && v <= 0 }
168
+ size_compare_results.any? { |v| v == -1 } ? -1 : 0
169
+ elsif size_compare_results.all? { |v| v && v >= 0 }
170
+ size_compare_results.any? { |v| v == 1 } ? 1 : 0
171
+ else
172
+ nil
173
+ end
174
+ end
175
+
176
+ def compare_when_keys_subset(hash_self, hash_other)
177
+ subset_keys = hash_self.keys
178
+ hash_other_subset = subset_keys.inject({}) { |t, k| t.merge(k => hash_other[k]) }
179
+ ret = compare_when_keys_same(hash_self, hash_other_subset)
180
+ ret && (ret <= 0 ? -1 : nil)
181
+ end
182
+
183
+ # @return [Hash<Object,Array<Object>>]
184
+ def merge_when_keys_diff!(hash_self, hash_other)
185
+ merge_able_lab = ->(to, from) do
186
+ from.keys.all? do |f_ask|
187
+ if to.any? { |k, _| f_ask <=> k }
188
+ true
189
+ else
190
+ if f_ask.data['type']
191
+ to.keys.none? { |e| e.data['type'] && e.data['type'] and e.data['type'] == f_ask.data['type'] }
192
+ else
193
+ true
194
+ end
195
+ end
196
+ end
197
+ end
198
+ merge_lab = ->(to, from) do
199
+ # 现将内容全部拼起来,然后合并资源
200
+ ret_hash = {}
201
+ to.each { |ask, ask_dup_list| ret_hash[ask] = ask_dup_list }
202
+ from.each do |ask, ask_dup_list|
203
+ # 是否有相同要求的资源要求
204
+ if ret_hash[ask]
205
+ unless ret_hash[ask].size >= ask_dup_list
206
+ ret_hash[ask] += ask_dup_list[ret_hash[ask].size..-1]
207
+ end
208
+ else
209
+ # 没有
210
+
211
+ # 是否总结果中有可合并的资源请求
212
+ merge_able_ask = ret_hash.keys.find { |a| a <=> ask }
213
+ if merge_able_ask
214
+ if merge_able_ask >= ask
215
+ if ret_hash[merge_able_ask].size < ask_dup_list.size
216
+ ret_hash[merge_able_ask] += ask_dup_list[ret_hash[merge_able_ask].size..-1]
217
+ end
218
+ else
219
+ if ret_hash[merge_able_ask].size <= ask_dup_list.size
220
+ ret_hash.delete(merge_able_ask)
221
+ ret_hash[ask] = ask_dup_list
222
+ else
223
+ ret_hash[ask] = ask_dup_list
224
+ ret_hash[merge_able_ask].pop(ask_dup_list.size)
225
+ end
226
+ end
227
+ else
228
+ ret_hash[ask] = ask_dup_list
229
+ end
230
+ end
231
+ end
232
+
233
+ ret_hash
234
+ end
235
+
236
+ if merge_able_lab.call(hash_self, hash_other)
237
+ merge_lab.call(hash_self, hash_other)
238
+ elsif merge_able_lab.call(hash_other, hash_self)
239
+ merge_lab.call(hash_other, hash_self)
240
+ else
241
+ raise('冲突较大,不能合并')
242
+ end
243
+ end
244
+
245
+ def merge(other)
246
+ all_self_hash = self.data.group_by { |e| e.to_s }
247
+ all_self_hash = Hash[all_self_hash.map { |k, v| [Ask.new(k), v] }]
248
+ all_other_hash = other.data.group_by { |e| e.to_s }
249
+ all_other_hash = Hash[all_other_hash.map { |k, v| [Ask.new(k), v] }]
250
+ all_self_hash_keys = Set.new all_self_hash.keys
251
+ all_other_hash_keys = Set.new all_other_hash.keys
252
+
253
+ merge_when_same_keys_lab = ->(keys) do
254
+ keys.inject({}) do |t, k|
255
+ if all_self_hash[k].size >= all_other_hash[k].size
256
+ t.merge k => all_self_hash[k]
257
+ else
258
+ t.merge k => all_other_hash[k]
259
+ end
260
+ end
261
+ end
262
+
263
+ result = if all_self_hash_keys == all_other_hash_keys
264
+ merge_when_same_keys_lab.call(all_self_hash_keys)
265
+ elsif all_self_hash_keys < all_other_hash_keys
266
+ ret = merge_when_same_keys_lab.call(all_self_hash_keys)
267
+ all_other_hash.merge(ret)
268
+ elsif all_self_hash_keys > all_other_hash_keys
269
+ ret = merge_when_same_keys_lab.call(all_other_hash_keys)
270
+ all_self_hash.merge(ret)
271
+ else
272
+ merge_when_keys_diff!(all_self_hash, all_other_hash)
273
+ end
274
+ new_instance = self.class.new([])
275
+
276
+
277
+ result.values.flatten.each { |v| new_instance.data.push v }
278
+ new_instance
279
+ end
280
+ end
281
+ end
282
+ end
@@ -0,0 +1,90 @@
1
+ require 'rake'
2
+ require 'rake/tasklib'
3
+ require 'yaml'
4
+ require 'java-properties'
5
+ require_relative 'rspec/distribute'
6
+
7
+ class Tes::Request::RakeTask < ::Rake::TaskLib
8
+ SUPPORT_FILE_TYPES = [:yaml, :yml, :json, :properties]
9
+
10
+ def initialize(name, type)
11
+ desc "Generate RSpec distribute jobs #{type} file for CI"
12
+ task name, [:project_dir, :type, :count, :version, :lang] do |_, task_args|
13
+ rspec_distribute = ::Tes::Request::RSpec::Distribute.new(task_args[:project_dir])
14
+ jobs = rspec_distribute.distribute_jobs(task_args[:type],
15
+ task_args[:count].to_i,
16
+ task_args[:version],
17
+ task_args[:lang])
18
+ target_dir = File.join(task_args[:project_dir], '.ci_jobs')
19
+ save_job_files(jobs, target_dir, type)
20
+ end
21
+ end
22
+
23
+ def spec_tag_param_str(tags)
24
+ case tags
25
+ when Array
26
+ tags.map { |t| "--tag #{t}" }.join(' ')
27
+ when String
28
+ "--tag #{tags}"
29
+ when nil
30
+ nil
31
+ else
32
+ raise("不支持的类型:#{tags.class}")
33
+ end
34
+ end
35
+
36
+ def save_job_files(jobs, target_dir, file_type)
37
+ unless SUPPORT_FILE_TYPES.include?(file_type)
38
+ raise(ArgumentError, "Not supported file type:#{file_type}!")
39
+ end
40
+
41
+ job_configs_for_ci = jobs.map { |j| gen_job_ci_params(j) }
42
+ FileUtils.rm_rf(target_dir)
43
+ FileUtils.mkdir(target_dir)
44
+ case file_type
45
+ when :json
46
+ save_file = File.join(target_dir, 'ci_tasks.json')
47
+ File.open(save_file, 'w') { |f| f.write job_configs_for_ci.to_json }
48
+ puts "Generated #{jobs.size} jobs, Stored in:#{save_file} ."
49
+ when :yml, :yaml
50
+ save_file = File.join(target_dir, 'ci_tasks.yml')
51
+ File.open(save_file, 'w') { |f| f.write job_configs_for_ci.to_yaml }
52
+ puts "Generated #{jobs.size} jobs, Stored in:#{save_file} ."
53
+ when :properties
54
+ job_configs_for_ci.each_with_index do |params, i|
55
+ file = File.join(target_dir, "#{i}.properties")
56
+ save_job_properties(params, file)
57
+ end
58
+ puts "Generated #{jobs.size} jobs, Stored in:#{target_dir}/*.properties ."
59
+ end
60
+ end
61
+
62
+ def save_job_properties(job_cfg, save_file)
63
+ # context = ["# gen at #{Time.now}"]
64
+ JavaProperties.write(job_cfg, save_file)
65
+ #
66
+ # context = ["# gen at #{Time.now}"]
67
+ # tag_opt_cli_args = spec_tag_param_str(job_cfg[:tag])
68
+ # rspec_param = if tag_opt_cli_args
69
+ # "RSPEC_PARAM = #{tag_opt_cli_args} \\\n\t#{piece[:files].join(" \\\n\t")}"
70
+ # else
71
+ # "RSPEC_PARAM = #{piece[:files].join(" \\\n\t")}"
72
+ # end
73
+ # context << "REQUEST_ASKS = #{piece[:profile].to_s(";\\\n\t")}"
74
+ # File.open(file, 'w') { |f| f.write context.join("\n") }
75
+ end
76
+
77
+ def get_job_rspec_run_args_str(job, split=' ')
78
+ tags_str = spec_tag_param_str(job[:tag])
79
+ paths_str = job[:specs].join(split)
80
+ tags_str ? (tags_str + split + paths_str) : paths_str
81
+ end
82
+
83
+ def get_job_env_profile_str(job, split=';')
84
+ job[:profile].to_s(split)
85
+ end
86
+
87
+ def gen_job_ci_params(job)
88
+ {'RSPEC_PARAM' => get_job_rspec_run_args_str(job), 'REQUEST_ASKS' => get_job_env_profile_str(job)}
89
+ end
90
+ end