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.
@@ -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