test_ids 0.8.0 → 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/config/shared_commands.rb +2 -1
- data/config/version.rb +1 -1
- data/lib/test_ids/allocator.rb +279 -80
- data/lib/test_ids/bin_array.rb +37 -0
- data/lib/test_ids/commands/repair.rb +60 -0
- data/lib/test_ids/configuration.rb +73 -7
- data/lib/test_ids/origen_testers/flow.rb +9 -0
- data/lib/test_ids.rb +13 -2
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 398ea9b7ec2c5bad27c399d18f0e94d55e505cdb
|
4
|
+
data.tar.gz: 7474a3c2854ad487f8433316c74098dddcae3f92
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 901efb539a198fa3ba56c50b948dd75a70217e5d7b03fb2c8febfa34e06e5cb69a4c3784e18c11c62677eda9b051c3c4f67b2609dcc071b166d64719ede8888f
|
7
|
+
data.tar.gz: 06cd20a662de6515f40674ef866ef5b6807e99658c9087b88dc3fa46b0943bc07f58bc5c6fdc66c0cb029a16a56fbfb89a68198fd1586068aa85f91eef1da738
|
data/config/shared_commands.rb
CHANGED
@@ -10,7 +10,7 @@ when "test_ids:rollback"
|
|
10
10
|
end
|
11
11
|
exit 0
|
12
12
|
|
13
|
-
when "test_ids:clear"
|
13
|
+
when "test_ids:clear", "test_ids:repair"
|
14
14
|
require "test_ids/commands/#{@command.split(':').last}"
|
15
15
|
exit 0
|
16
16
|
|
@@ -18,6 +18,7 @@ else
|
|
18
18
|
@plugin_commands << <<-EOT
|
19
19
|
test_ids:rollback Rollback the TestIds store to the given commit ID
|
20
20
|
test_ids:clear Clear the assignment database for bins, softbins, numbers or all
|
21
|
+
test_ids:repair Repair the given database, see -h for more
|
21
22
|
EOT
|
22
23
|
|
23
24
|
end
|
data/config/version.rb
CHANGED
data/lib/test_ids/allocator.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'json'
|
1
2
|
module TestIds
|
2
3
|
# The allocator is responsible for assigning new numbers and keeping a record of
|
3
4
|
# existing assignments.
|
@@ -5,7 +6,7 @@ module TestIds
|
|
5
6
|
# There is one allocator instance per configuration, and each has its own database
|
6
7
|
# file.
|
7
8
|
class Allocator
|
8
|
-
STORE_FORMAT_REVISION =
|
9
|
+
STORE_FORMAT_REVISION = 2
|
9
10
|
|
10
11
|
attr_reader :config
|
11
12
|
|
@@ -13,6 +14,54 @@ module TestIds
|
|
13
14
|
@config = configuration
|
14
15
|
end
|
15
16
|
|
17
|
+
# Allocates a softbin number from the range specified in the test flow
|
18
|
+
# It also keeps a track of the last softbin assigned out from a particular range
|
19
|
+
# and uses that to increment the pointers accordingly.
|
20
|
+
# If a numeric number is passed to the softbin, it uses that number.
|
21
|
+
# The configuration for the TestId plugin needs to pass in the bin number and the options from the test flow
|
22
|
+
# For this method to work as intended.
|
23
|
+
def next_in_range(range, options)
|
24
|
+
range_item(range, options)
|
25
|
+
end
|
26
|
+
|
27
|
+
def range_item(range, options)
|
28
|
+
orig_options = options.dup
|
29
|
+
# Create an alias for the databse that stores the pointers per range
|
30
|
+
rangehash = store['pointers']['ranges'] ||= {}
|
31
|
+
# Check the database to see if the passed in range has already been included in the database hash
|
32
|
+
if rangehash.key?(:"#{range}")
|
33
|
+
# Read out the database hash to see what the last_softbin given out was for that range.
|
34
|
+
# This hash is updated whenever a new softbin is assigned, so it should have the updated values for each range.
|
35
|
+
previous_assigned_value = rangehash[:"#{range}"].to_i
|
36
|
+
# Now calculate the new pointer.
|
37
|
+
@pointer = previous_assigned_value - range.min
|
38
|
+
# Check if the last_softbin given out is the same as the range[@pointer],
|
39
|
+
# if so increment pointer by softbin size, default size is 1, config.softbins.size is configurable.
|
40
|
+
# from example above, pointer was calculated as 1,range[1] is 10101 and is same as last_softbin, so pointer is incremented
|
41
|
+
# and new value is assigned to the softbin.
|
42
|
+
if previous_assigned_value == range.to_a[@pointer]
|
43
|
+
@pointer += options[:size]
|
44
|
+
assigned_value = range.to_a[@pointer]
|
45
|
+
else
|
46
|
+
# Because of the pointer calculations above, I don't think it will ever reach here, has not in my test cases so far!
|
47
|
+
assigned_value = range.to_a[@pointer]
|
48
|
+
end
|
49
|
+
# Now update the database pointers to point to the lastest assigned softbin for a given range.
|
50
|
+
rangehash.merge!("#{range}": "#{range.to_a[@pointer]}")
|
51
|
+
else
|
52
|
+
# This is the case for a brand new range that has not been passed before
|
53
|
+
# We start from the first value as the assigned softbin and update the database to reflect.
|
54
|
+
@pointer = 0
|
55
|
+
rangehash.merge!("#{range}": "#{range.to_a[@pointer]}")
|
56
|
+
assigned_value = range.to_a[@pointer]
|
57
|
+
end
|
58
|
+
unless !assigned_value.nil? && assigned_value.between?(range.min, range.max)
|
59
|
+
Origen.log.error 'Assigned value not in range'
|
60
|
+
fail
|
61
|
+
end
|
62
|
+
assigned_value
|
63
|
+
end
|
64
|
+
|
16
65
|
# Main method to inject generated bin and test numbers, the given
|
17
66
|
# options instance is modified accordingly
|
18
67
|
def allocate(instance, options)
|
@@ -44,19 +93,19 @@ module TestIds
|
|
44
93
|
softbin_size = options[:softbin_size] || config.softbins.size
|
45
94
|
number_size = options[:number_size] || config.numbers.size
|
46
95
|
|
47
|
-
bin = store['assigned']['
|
48
|
-
softbin = store['assigned']['
|
49
|
-
number = store['assigned']['
|
96
|
+
bin = store['assigned']['bins'][bin_id] ||= {}
|
97
|
+
softbin = store['assigned']['softbins'][softbin_id] ||= {}
|
98
|
+
number = store['assigned']['numbers'][number_id] ||= {}
|
50
99
|
|
51
100
|
# If the user has supplied any of these, that number should be used
|
52
101
|
# and reserved so that it is not automatically generated later
|
53
102
|
if options[:bin] && options[:bin].is_a?(Numeric)
|
54
103
|
bin['number'] = options[:bin]
|
55
104
|
bin['size'] = bin_size
|
56
|
-
store['manually_assigned']['
|
105
|
+
store['manually_assigned']['bins'][options[:bin].to_s] = true
|
57
106
|
# Regenerate the bin if the original allocation has since been applied
|
58
107
|
# manually elsewhere
|
59
|
-
elsif store['manually_assigned']['
|
108
|
+
elsif store['manually_assigned']['bins'][bin['number'].to_s]
|
60
109
|
bin['number'] = nil
|
61
110
|
bin['size'] = nil
|
62
111
|
# Also regenerate these as they could be a function of the bin
|
@@ -72,8 +121,8 @@ module TestIds
|
|
72
121
|
if options[:softbin] && options[:softbin].is_a?(Numeric)
|
73
122
|
softbin['number'] = options[:softbin]
|
74
123
|
softbin['size'] = softbin_size
|
75
|
-
store['manually_assigned']['
|
76
|
-
elsif store['manually_assigned']['
|
124
|
+
store['manually_assigned']['softbins'][options[:softbin].to_s] = true
|
125
|
+
elsif store['manually_assigned']['softbins'][softbin['number'].to_s]
|
77
126
|
softbin['number'] = nil
|
78
127
|
softbin['size'] = nil
|
79
128
|
# Also regenerate the number as it could be a function of the softbin
|
@@ -85,39 +134,46 @@ module TestIds
|
|
85
134
|
if options[:number] && options[:number].is_a?(Numeric)
|
86
135
|
number['number'] = options[:number]
|
87
136
|
number['size'] = number_size
|
88
|
-
store['manually_assigned']['
|
89
|
-
elsif store['manually_assigned']['
|
137
|
+
store['manually_assigned']['numbers'][options[:number].to_s] = true
|
138
|
+
elsif store['manually_assigned']['numbers'][number['number'].to_s]
|
90
139
|
number['number'] = nil
|
91
140
|
number['size'] = nil
|
141
|
+
# Also regenerate the softbin as it could be a function of the number
|
142
|
+
if config.softbins.function?
|
143
|
+
softbin['number'] = nil
|
144
|
+
softbin['size'] = nil
|
145
|
+
end
|
92
146
|
end
|
93
147
|
|
94
148
|
# Otherwise generate the missing ones
|
95
|
-
bin['number'] ||= allocate_bin(size: bin_size)
|
149
|
+
bin['number'] ||= allocate_bin(options.merge(size: bin_size))
|
96
150
|
bin['size'] ||= bin_size
|
97
151
|
# If the softbin is based on the test number, then need to calculate the
|
98
|
-
# test number first
|
99
|
-
if
|
100
|
-
|
152
|
+
# test number first.
|
153
|
+
# Also do the number first if the softbin is a callback and the number is not.
|
154
|
+
if (config.softbins.algorithm && config.softbins.algorithm.to_s =~ /n/) ||
|
155
|
+
(config.softbins.callback && !config.numbers.function?)
|
156
|
+
number['number'] ||= allocate_number(options.merge(bin: bin['number'], size: number_size))
|
101
157
|
number['size'] ||= number_size
|
102
|
-
softbin['number'] ||= allocate_softbin(bin: bin['number'], number: number['number'], size: softbin_size)
|
158
|
+
softbin['number'] ||= allocate_softbin(options.merge(bin: bin['number'], number: number['number'], size: softbin_size))
|
103
159
|
softbin['size'] ||= softbin_size
|
104
160
|
else
|
105
|
-
softbin['number'] ||= allocate_softbin(bin: bin['number'], size: softbin_size)
|
161
|
+
softbin['number'] ||= allocate_softbin(options.merge(bin: bin['number'], size: softbin_size))
|
106
162
|
softbin['size'] ||= softbin_size
|
107
|
-
number['number'] ||= allocate_number(bin: bin['number'], softbin: softbin['number'], size: number_size)
|
163
|
+
number['number'] ||= allocate_number(options.merge(bin: bin['number'], softbin: softbin['number'], size: number_size))
|
108
164
|
number['size'] ||= number_size
|
109
165
|
end
|
110
166
|
|
111
167
|
# Record that there has been a reference to the final numbers
|
112
168
|
time = Time.now.to_f
|
113
169
|
bin_size.times do |i|
|
114
|
-
store['references']['
|
170
|
+
store['references']['bins'][(bin['number'] + i).to_s] = time if bin['number'] && options[:bin] != :none
|
115
171
|
end
|
116
172
|
softbin_size.times do |i|
|
117
|
-
store['references']['
|
173
|
+
store['references']['softbins'][(softbin['number'] + i).to_s] = time if softbin['number'] && options[:softbin] != :none
|
118
174
|
end
|
119
175
|
number_size.times do |i|
|
120
|
-
store['references']['
|
176
|
+
store['references']['numbers'][(number['number'] + i).to_s] = time if number['number'] && options[:number] != :none
|
121
177
|
end
|
122
178
|
|
123
179
|
# Update the supplied options hash that will be forwarded to the program generator
|
@@ -134,25 +190,19 @@ module TestIds
|
|
134
190
|
options[:number_size] = number['size']
|
135
191
|
end
|
136
192
|
|
137
|
-
## If reallocation is on, then check if the generated numbers are compliant, if not
|
138
|
-
## clear them and go back around again to generate a new set
|
139
|
-
# if TestIds.reallocate_non_compliant
|
140
|
-
# if !config.bins.function?
|
141
|
-
# if !config.bins.compliant?(options[:bin])
|
142
|
-
# store["assigned"]["bin"].delete(bin_id)
|
143
|
-
# return allocate(instance, orig_options)
|
144
|
-
# end
|
145
|
-
# end
|
146
|
-
# end
|
147
|
-
|
148
193
|
options
|
149
194
|
end
|
150
195
|
|
151
196
|
def store
|
152
197
|
@store ||= begin
|
153
|
-
|
198
|
+
if file && File.exist?(file)
|
199
|
+
lines = File.readlines(file)
|
200
|
+
# Remove any header comment lines since these are not valid JSON
|
201
|
+
lines.shift while lines.first =~ /^\/\// && !lines.empty?
|
202
|
+
s = JSON.load(lines.join("\n"))
|
203
|
+
end
|
154
204
|
if s
|
155
|
-
|
205
|
+
unless s['format_revision']
|
156
206
|
# Upgrade the original store format
|
157
207
|
t = { 'bin' => {}, 'softbin' => {}, 'number' => {} }
|
158
208
|
s['tests'].each do |name, numbers|
|
@@ -161,48 +211,119 @@ module TestIds
|
|
161
211
|
t['number'][name] = { 'number' => numbers['number'], 'size' => 1 }
|
162
212
|
end
|
163
213
|
s = {
|
164
|
-
'format_revision' =>
|
214
|
+
'format_revision' => 1,
|
165
215
|
'assigned' => t,
|
166
216
|
'manually_assigned' => s['manually_assigned'],
|
167
217
|
'pointers' => s['pointers'],
|
168
218
|
'references' => s['references']
|
169
219
|
}
|
170
220
|
end
|
171
|
-
|
172
|
-
|
173
|
-
|
221
|
+
# Change the keys to plural versions, this makes it easier to search for in the file
|
222
|
+
# since 'number' is used within individual records
|
223
|
+
if s['format_revision'] == 1
|
224
|
+
s = {
|
225
|
+
'format_revision' => 2,
|
226
|
+
'configuration' => nil,
|
227
|
+
'pointers' => { 'bins' => s['pointers']['bin'], 'softbins' => s['pointers']['softbin'], 'numbers' => s['pointers']['number'] },
|
228
|
+
'assigned' => { 'bins' => s['assigned']['bin'], 'softbins' => s['assigned']['softbin'], 'numbers' => s['assigned']['number'] },
|
229
|
+
'manually_assigned' => { 'bins' => s['manually_assigned']['bin'], 'softbins' => s['manually_assigned']['softbin'], 'numbers' => s['manually_assigned']['number'] },
|
230
|
+
'references' => { 'bins' => s['references']['bin'], 'softbins' => s['references']['softbin'], 'numbers' => s['references']['number'] }
|
231
|
+
}
|
232
|
+
end
|
233
|
+
|
234
|
+
@last_bin = s['pointers']['bins']
|
235
|
+
@last_softbin = s['pointers']['softbins']
|
236
|
+
@last_number = s['pointers']['numbers']
|
174
237
|
s
|
175
238
|
else
|
176
239
|
{
|
177
240
|
'format_revision' => STORE_FORMAT_REVISION,
|
178
|
-
'
|
179
|
-
'
|
180
|
-
'
|
181
|
-
'
|
241
|
+
'configuration' => nil,
|
242
|
+
'pointers' => { 'bins' => nil, 'softbins' => nil, 'numbers' => nil },
|
243
|
+
'assigned' => { 'bins' => {}, 'softbins' => {}, 'numbers' => {} },
|
244
|
+
'manually_assigned' => { 'bins' => {}, 'softbins' => {}, 'numbers' => {} },
|
245
|
+
'references' => { 'bins' => {}, 'softbins' => {}, 'numbers' => {} }
|
182
246
|
}
|
183
247
|
end
|
184
248
|
end
|
185
249
|
end
|
186
250
|
|
251
|
+
def repair(options = {})
|
252
|
+
#####################################################################
|
253
|
+
# Add any numbers that are missing from the references pool if the
|
254
|
+
# allocator has moved onto the reclamation phase
|
255
|
+
#####################################################################
|
256
|
+
{ 'bins' => 'bins', 'softbins' => 'softbins', 'numbers' => 'test_numbers' }.each do |type, name|
|
257
|
+
if !config.send(type).function? && store['pointers'][type] == 'done'
|
258
|
+
Origen.log.info "Checking for missing #{name}..."
|
259
|
+
recovered = add_missing_references(config.send(type), store['references'][type])
|
260
|
+
if recovered == 0
|
261
|
+
Origen.log.info " All #{name} are already available."
|
262
|
+
else
|
263
|
+
Origen.log.success " Another #{recovered} #{name} have been made available!"
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
#####################################################################
|
269
|
+
# Check that all assignments are valid based on the current config,
|
270
|
+
# if not remove them and they will be re-allocated next time
|
271
|
+
#####################################################################
|
272
|
+
{ 'bins' => 'bins', 'softbins' => 'softbins', 'numbers' => 'test_numbers' }.each do |type, name|
|
273
|
+
next if config.send(type).function?
|
274
|
+
Origen.log.info "Checking all #{name} assignments are valid..."
|
275
|
+
also_remove_from = []
|
276
|
+
if type == 'bin'
|
277
|
+
also_remove_from << store['assigned']['softbins'] if config.softbins.function?
|
278
|
+
also_remove_from << store['assigned']['numbers'] if config.numbers.function?
|
279
|
+
elsif type == 'softbin'
|
280
|
+
also_remove_from << store['assigned']['numbers'] if config.numbers.function?
|
281
|
+
else
|
282
|
+
also_remove_from << store['assigned']['softbins'] if config.softbins.function?
|
283
|
+
end
|
284
|
+
removed = remove_invalid_assignments(config.send(type), store['assigned'][type], store['manually_assigned'][type], also_remove_from)
|
285
|
+
if removed == 0
|
286
|
+
Origen.log.info " All #{name} assignments are already valid."
|
287
|
+
else
|
288
|
+
Origen.log.success " #{removed} #{name} assignments have been removed!"
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
#####################################################################
|
293
|
+
# Check that all references are valid based on the current config,
|
294
|
+
# if not remove them
|
295
|
+
#####################################################################
|
296
|
+
{ 'bins' => 'bins', 'softbins' => 'softbins', 'numbers' => 'test_numbers' }.each do |type, name|
|
297
|
+
next if config.send(type).function?
|
298
|
+
Origen.log.info "Checking all #{name} references are valid..."
|
299
|
+
removed = remove_invalid_references(config.send(type), store['references'][type], store['manually_assigned'][type])
|
300
|
+
if removed == 0
|
301
|
+
Origen.log.info " All #{name} references are already valid."
|
302
|
+
else
|
303
|
+
Origen.log.success " #{removed} #{name} references have been removed!"
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
187
308
|
# Clear the :bins, :softbins and/or :numbers by setting the options for each item to true
|
188
309
|
def clear(options)
|
189
310
|
if options[:softbin] || options[:softbins]
|
190
|
-
store['assigned']['
|
191
|
-
store['manually_assigned']['
|
192
|
-
store['pointers']['
|
193
|
-
store['references']['
|
311
|
+
store['assigned']['softbins'] = {}
|
312
|
+
store['manually_assigned']['softbins'] = {}
|
313
|
+
store['pointers']['softbins'] = nil
|
314
|
+
store['references']['softbins'] = {}
|
194
315
|
end
|
195
316
|
if options[:bin] || options[:bins]
|
196
|
-
store['assigned']['
|
197
|
-
store['manually_assigned']['
|
198
|
-
store['pointers']['
|
199
|
-
store['references']['
|
317
|
+
store['assigned']['bins'] = {}
|
318
|
+
store['manually_assigned']['bins'] = {}
|
319
|
+
store['pointers']['bins'] = nil
|
320
|
+
store['references']['bins'] = {}
|
200
321
|
end
|
201
322
|
if options[:number] || options[:numbers]
|
202
|
-
store['assigned']['
|
203
|
-
store['manually_assigned']['
|
204
|
-
store['pointers']['
|
205
|
-
store['references']['
|
323
|
+
store['assigned']['numbers'] = {}
|
324
|
+
store['manually_assigned']['numbers'] = {}
|
325
|
+
store['pointers']['numbers'] = nil
|
326
|
+
store['references']['numbers'] = {}
|
206
327
|
end
|
207
328
|
end
|
208
329
|
|
@@ -212,9 +333,38 @@ module TestIds
|
|
212
333
|
# Ensure the current store has been loaded before we try to re-write it, this
|
213
334
|
# is necessary if the program generator has crashed before creating a test
|
214
335
|
store
|
336
|
+
store['configuration'] = config
|
215
337
|
p = Pathname.new(file)
|
216
338
|
FileUtils.mkdir_p(p.dirname)
|
217
|
-
File.open(p, 'w')
|
339
|
+
File.open(p, 'w') do |f|
|
340
|
+
f.puts '// The structure of this file is as follows:'
|
341
|
+
f.puts '//'
|
342
|
+
f.puts '// {'
|
343
|
+
f.puts '// // A revision number used by TestIDs to identify the format of this file'
|
344
|
+
f.puts "// 'format_revision' => STORE_FORMAT_REVISION,"
|
345
|
+
f.puts '//'
|
346
|
+
f.puts '// // Captures the configuration that was used the last time this database was updated.'
|
347
|
+
f.puts "// 'configuration' => { 'bins' => {}, 'softbins' => {}, 'numbers' => {} },"
|
348
|
+
f.puts '//'
|
349
|
+
f.puts '// // If some number are still to be allocated, these point to the last number given out.'
|
350
|
+
f.puts '// // If all numbers have been allocated and we are now on the reclamation phase, the pointer'
|
351
|
+
f.puts '// // will contain "done".'
|
352
|
+
f.puts "// 'pointers' => { 'bins' => nil, 'softbins' => nil, 'numbers' => nil, 'ranges' => nil },"
|
353
|
+
f.puts '//'
|
354
|
+
f.puts '// // This is the record of all numbers which have been previously assigned.'
|
355
|
+
f.puts "// 'assigned' => { 'bins' => {}, 'softbins' => {}, 'numbers' => {} },"
|
356
|
+
f.puts '//'
|
357
|
+
f.puts '// // This is a record of any numbers which have been manually assigned.'
|
358
|
+
f.puts "// 'manually_assigned' => { 'bins' => {}, 'softbins' => {}, 'numbers' => {} },"
|
359
|
+
f.puts '//'
|
360
|
+
f.puts '// // This contains all assigned numbers with a timestamp of when they were last referenced.'
|
361
|
+
f.puts '// // When numbers need to be reclaimed, they will be taken from the bottom of this list, i.e.'
|
362
|
+
f.puts '// // the numbers which have not been used for the longest time, e.g. because the test they'
|
363
|
+
f.puts '// // were assigned to has since been removed.'
|
364
|
+
f.puts "// 'references' => { 'bins' => {}, 'softbins' => {}, 'numbers' => {} }"
|
365
|
+
f.puts '// }'
|
366
|
+
f.puts JSON.pretty_generate(store)
|
367
|
+
end
|
218
368
|
end
|
219
369
|
end
|
220
370
|
|
@@ -228,37 +378,86 @@ module TestIds
|
|
228
378
|
config.id
|
229
379
|
end
|
230
380
|
|
381
|
+
# @api private
|
382
|
+
def load_configuration_from_store
|
383
|
+
config.load_from_serialized(store['configuration']) if store['configuration']
|
384
|
+
end
|
385
|
+
|
231
386
|
private
|
232
387
|
|
388
|
+
def remove_invalid_references(config_item, references, manually_assigned)
|
389
|
+
removed = 0
|
390
|
+
references.each do |num, time|
|
391
|
+
unless config_item.valid?(num.to_i) || manually_assigned[num]
|
392
|
+
removed += 1
|
393
|
+
references.delete(num)
|
394
|
+
end
|
395
|
+
end
|
396
|
+
removed
|
397
|
+
end
|
398
|
+
|
399
|
+
def remove_invalid_assignments(config_item, assigned, manually_assigned, also_remove_from)
|
400
|
+
removed = 0
|
401
|
+
assigned.each do |id, a|
|
402
|
+
a['size'].times do |i|
|
403
|
+
unless config_item.valid?(a['number'] + i) || manually_assigned[(a['number'] + i).to_s]
|
404
|
+
removed += 1
|
405
|
+
assigned.delete(id)
|
406
|
+
also_remove_from.each { |a| a.delete(id) }
|
407
|
+
break
|
408
|
+
end
|
409
|
+
end
|
410
|
+
end
|
411
|
+
removed
|
412
|
+
end
|
413
|
+
|
414
|
+
def add_missing_references(config_item, references)
|
415
|
+
recovered = 0
|
416
|
+
a_long_time_ago = Time.new(2000, 1, 1).to_f
|
417
|
+
config_item.yield_all do |i|
|
418
|
+
i = i.to_s
|
419
|
+
unless references[i]
|
420
|
+
references[i] = a_long_time_ago
|
421
|
+
recovered += 1
|
422
|
+
end
|
423
|
+
end
|
424
|
+
recovered
|
425
|
+
end
|
426
|
+
|
233
427
|
# Returns the next available bin in the pool, if they have all been given out
|
234
428
|
# the one that hasn't been used for the longest time will be given out
|
235
429
|
def allocate_bin(options)
|
236
|
-
|
237
|
-
|
430
|
+
# Not sure if this is the right way. IMO the following are true:
|
431
|
+
# 1. config.bins will have a callback only when ranges are specified.
|
432
|
+
# 2. If config.bins is empty but config.bins is not a callback, return nil to maintain functionality as before.
|
433
|
+
return nil if config.bins.empty? && !config.bins.callback
|
434
|
+
if store['pointers']['bins'] == 'done'
|
238
435
|
reclaim_bin(options)
|
436
|
+
elsif callback = config.bins.callback
|
437
|
+
callback.call(options)
|
239
438
|
else
|
240
439
|
b = config.bins.include.next(after: @last_bin, size: options[:size])
|
241
440
|
@last_bin = nil
|
242
|
-
while b && (store['manually_assigned']['
|
441
|
+
while b && (store['manually_assigned']['bins'][b.to_s] || config.bins.exclude.include?(b))
|
243
442
|
b = config.bins.include.next(size: options[:size])
|
244
443
|
end
|
245
444
|
# When no bin is returned it means we have used them all, all future generation
|
246
445
|
# now switches to reclaim mode
|
247
446
|
if b
|
248
|
-
store['pointers']['
|
447
|
+
store['pointers']['bins'] = b
|
249
448
|
else
|
250
|
-
store['pointers']['
|
449
|
+
store['pointers']['bins'] = 'done'
|
251
450
|
reclaim_bin(options)
|
252
451
|
end
|
253
452
|
end
|
254
453
|
end
|
255
454
|
|
256
455
|
def reclaim_bin(options)
|
257
|
-
store['references']['
|
456
|
+
store['references']['bins'] = store['references']['bins'].sort_by { |k, v| v }.to_h
|
258
457
|
if options[:size] == 1
|
259
|
-
store['references']['
|
458
|
+
store['references']['bins'].first[0].to_i
|
260
459
|
else
|
261
|
-
reclaim(store['references']['
|
460
|
+
reclaim(store['references']['bins'], options)
|
262
461
|
end
|
263
462
|
end
|
264
463
|
|
@@ -288,7 +487,7 @@ module TestIds
|
|
288
487
|
end
|
289
488
|
if number =~ /(x+)/
|
290
489
|
max_counter_size = Regexp.last_match(1).size
|
291
|
-
refs = store['references']['
|
490
|
+
refs = store['references']['softbins']
|
292
491
|
i = 0
|
293
492
|
possible = []
|
294
493
|
proposal = number.sub(/x+/, i.to_s.rjust(max_counter_size, '0'))
|
@@ -315,22 +514,22 @@ module TestIds
|
|
315
514
|
end
|
316
515
|
number.to_i
|
317
516
|
elsif callback = config.softbins.callback
|
318
|
-
callback.call(bin)
|
517
|
+
callback.call(bin, options)
|
319
518
|
else
|
320
|
-
if store['pointers']['
|
519
|
+
if store['pointers']['softbins'] == 'done'
|
321
520
|
reclaim_softbin(options)
|
322
521
|
else
|
323
522
|
b = config.softbins.include.next(after: @last_softbin, size: options[:size])
|
324
523
|
@last_softbin = nil
|
325
|
-
while b && (store['manually_assigned']['
|
524
|
+
while b && (store['manually_assigned']['softbins'][b.to_s] || config.softbins.exclude.include?(b))
|
326
525
|
b = config.softbins.include.next(size: options[:size])
|
327
526
|
end
|
328
527
|
# When no softbin is returned it means we have used them all, all future generation
|
329
528
|
# now switches to reclaim mode
|
330
529
|
if b
|
331
|
-
store['pointers']['
|
530
|
+
store['pointers']['softbins'] = b
|
332
531
|
else
|
333
|
-
store['pointers']['
|
532
|
+
store['pointers']['softbins'] = 'done'
|
334
533
|
reclaim_softbin(options)
|
335
534
|
end
|
336
535
|
end
|
@@ -338,11 +537,11 @@ module TestIds
|
|
338
537
|
end
|
339
538
|
|
340
539
|
def reclaim_softbin(options)
|
341
|
-
store['references']['
|
540
|
+
store['references']['softbins'] = store['references']['softbins'].sort_by { |k, v| v }.to_h
|
342
541
|
if options[:size] == 1
|
343
|
-
store['references']['
|
542
|
+
store['references']['softbins'].first[0].to_i
|
344
543
|
else
|
345
|
-
reclaim(store['references']['
|
544
|
+
reclaim(store['references']['softbins'], options)
|
346
545
|
end
|
347
546
|
end
|
348
547
|
|
@@ -372,7 +571,7 @@ module TestIds
|
|
372
571
|
end
|
373
572
|
if number =~ /(x+)/
|
374
573
|
max_counter_size = Regexp.last_match(1).size
|
375
|
-
refs = store['references']['
|
574
|
+
refs = store['references']['numbers']
|
376
575
|
i = 0
|
377
576
|
possible = []
|
378
577
|
proposal = number.sub(/x+/, i.to_s.rjust(max_counter_size, '0'))
|
@@ -399,22 +598,22 @@ module TestIds
|
|
399
598
|
fail "Unknown test number algorithm: #{algo}"
|
400
599
|
end
|
401
600
|
elsif callback = config.numbers.callback
|
402
|
-
callback.call(bin, softbin)
|
601
|
+
callback.call(bin, softbin, options)
|
403
602
|
else
|
404
|
-
if store['pointers']['
|
603
|
+
if store['pointers']['numbers'] == 'done'
|
405
604
|
reclaim_number(options)
|
406
605
|
else
|
407
606
|
b = config.numbers.include.next(after: @last_number, size: options[:size])
|
408
607
|
@last_number = nil
|
409
|
-
while b && (store['manually_assigned']['
|
608
|
+
while b && (store['manually_assigned']['numbers'][b.to_s] || config.numbers.exclude.include?(b))
|
410
609
|
b = config.numbers.include.next(size: options[:size])
|
411
610
|
end
|
412
611
|
# When no number is returned it means we have used them all, all future generation
|
413
612
|
# now switches to reclaim mode
|
414
613
|
if b
|
415
|
-
store['pointers']['
|
614
|
+
store['pointers']['numbers'] = b
|
416
615
|
else
|
417
|
-
store['pointers']['
|
616
|
+
store['pointers']['numbers'] = 'done'
|
418
617
|
reclaim_number(options)
|
419
618
|
end
|
420
619
|
end
|
@@ -422,11 +621,11 @@ module TestIds
|
|
422
621
|
end
|
423
622
|
|
424
623
|
def reclaim_number(options)
|
425
|
-
store['references']['
|
624
|
+
store['references']['numbers'] = store['references']['numbers'].sort_by { |k, v| v }.to_h
|
426
625
|
if options[:size] == 1
|
427
|
-
store['references']['
|
626
|
+
store['references']['numbers'].first[0].to_i
|
428
627
|
else
|
429
|
-
reclaim(store['references']['
|
628
|
+
reclaim(store['references']['numbers'], options)
|
430
629
|
end
|
431
630
|
end
|
432
631
|
|
data/lib/test_ids/bin_array.rb
CHANGED
@@ -115,6 +115,43 @@ module TestIds
|
|
115
115
|
end
|
116
116
|
end
|
117
117
|
|
118
|
+
# Yields all number to the given block, one at a time
|
119
|
+
def yield_all
|
120
|
+
p = @pointer
|
121
|
+
nx = @next
|
122
|
+
@pointer = nil
|
123
|
+
@next = nil
|
124
|
+
while n = self.next
|
125
|
+
yield n
|
126
|
+
end
|
127
|
+
@pointer = p
|
128
|
+
@next = nx
|
129
|
+
nil
|
130
|
+
end
|
131
|
+
|
132
|
+
def to_json(*a)
|
133
|
+
@store.to_json(*a)
|
134
|
+
end
|
135
|
+
|
136
|
+
# @api private
|
137
|
+
def load_from_serialized(o)
|
138
|
+
@store = []
|
139
|
+
o.each do |i|
|
140
|
+
if i.is_a?(Numeric) || i.is_a?(Range)
|
141
|
+
self.<<(i)
|
142
|
+
elsif i.is_a?(String)
|
143
|
+
# JSON does not serialize ranges well, take care of it here
|
144
|
+
if i =~ /^(\d+)\.\.(\d+)$/
|
145
|
+
self.<<((Regexp.last_match(1).to_i)..(Regexp.last_match(2).to_i))
|
146
|
+
else
|
147
|
+
fail "Unknown bin array object type (#{o.class}): #{o}"
|
148
|
+
end
|
149
|
+
else
|
150
|
+
fail "Unknown bin array object type (#{o.class}): #{o}"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
118
155
|
private
|
119
156
|
|
120
157
|
def previous_pointer(i)
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
options = {}
|
4
|
+
|
5
|
+
# App options are options that the application can supply to extend this command
|
6
|
+
app_options = @application_options || []
|
7
|
+
opt_parser = OptionParser.new do |opts|
|
8
|
+
opts.banner = <<-EOT
|
9
|
+
Performs maintenance on the given TestId database.
|
10
|
+
|
11
|
+
Usage: origen test_ids:repair ID [options]
|
12
|
+
|
13
|
+
EOT
|
14
|
+
# opts.on('--bins', 'Clear the bin database') { options[:bins] = true }
|
15
|
+
# opts.on('--softbins', 'Clear the softbin database') { options[:softbins] = true }
|
16
|
+
# opts.on('--numbers', 'Clear the test number database') { options[:numbers] = true }
|
17
|
+
opts.on('-d', '--debugger', 'Enable the debugger') { options[:debugger] = true }
|
18
|
+
app_options.each do |app_option|
|
19
|
+
opts.on(*app_option) {}
|
20
|
+
end
|
21
|
+
opts.separator ''
|
22
|
+
opts.on('-h', '--help', 'Show this message') { puts opts; exit 0 }
|
23
|
+
end
|
24
|
+
|
25
|
+
opt_parser.parse! ARGV
|
26
|
+
|
27
|
+
local = TestIds::Git.path_to_local
|
28
|
+
git = TestIds::Git.new(local: local)
|
29
|
+
TestIds.repo = git.repo.remote.url
|
30
|
+
git.get_lock
|
31
|
+
rollback_id = nil
|
32
|
+
stat = 0
|
33
|
+
begin
|
34
|
+
# Get the commit before the lock to give the user later
|
35
|
+
rollback_id = git.repo.object('HEAD^').sha[0, 11]
|
36
|
+
if ARGV.empty?
|
37
|
+
puts 'You must supply the ID of the configuration database that you wish to repair'
|
38
|
+
exit 1
|
39
|
+
end
|
40
|
+
ARGV.each do |id|
|
41
|
+
a = TestIds.load_allocator(id)
|
42
|
+
if a
|
43
|
+
a.repair(options)
|
44
|
+
a.save
|
45
|
+
else
|
46
|
+
Origen.log.error "A configuration database named #{id} was not found!"
|
47
|
+
stat = 1
|
48
|
+
end
|
49
|
+
end
|
50
|
+
ensure
|
51
|
+
git.publish
|
52
|
+
end
|
53
|
+
if rollback_id
|
54
|
+
puts
|
55
|
+
puts 'TestIDs database repaired as requested, you can rollback this change by running:'
|
56
|
+
puts
|
57
|
+
puts " origen test_ids:rollback #{rollback_id}"
|
58
|
+
puts
|
59
|
+
end
|
60
|
+
exit stat
|
@@ -25,14 +25,56 @@ module TestIds
|
|
25
25
|
!!algorithm || !!callback
|
26
26
|
end
|
27
27
|
|
28
|
-
|
29
|
-
|
28
|
+
def valid?(number)
|
29
|
+
if function?
|
30
|
+
fail 'valid? is not supported for algorithm or callback-based assignments'
|
31
|
+
end
|
32
|
+
number = number.to_i
|
33
|
+
include.include?(number) && !exclude.include?(number)
|
34
|
+
end
|
30
35
|
|
31
36
|
def freeze
|
32
37
|
@include.freeze
|
33
38
|
@exclude.freeze
|
34
39
|
super
|
35
40
|
end
|
41
|
+
|
42
|
+
# @api private
|
43
|
+
def load_from_serialized(o)
|
44
|
+
if o.is_a?(Hash)
|
45
|
+
@size = o['size']
|
46
|
+
@include.load_from_serialized(o['include'])
|
47
|
+
@exclude.load_from_serialized(o['exclude'])
|
48
|
+
elsif o == 'callback'
|
49
|
+
callback do
|
50
|
+
fail 'The callback for this configuration is not available!'
|
51
|
+
end
|
52
|
+
else
|
53
|
+
self.algorithm = o
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_json(*a)
|
58
|
+
if callback
|
59
|
+
'callback'.to_json(*a)
|
60
|
+
elsif algorithm
|
61
|
+
algorithm.to_s.to_json(*a)
|
62
|
+
else
|
63
|
+
{
|
64
|
+
'include' => include,
|
65
|
+
'exclude' => exclude,
|
66
|
+
'size' => size
|
67
|
+
}.to_json(*a)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Yields all included numbers to the given block, one at a time
|
72
|
+
def yield_all
|
73
|
+
include.yield_all do |i|
|
74
|
+
yield i unless exclude.include?(i)
|
75
|
+
end
|
76
|
+
nil
|
77
|
+
end
|
36
78
|
end
|
37
79
|
|
38
80
|
attr_reader :allocator
|
@@ -46,11 +88,15 @@ module TestIds
|
|
46
88
|
@id
|
47
89
|
end
|
48
90
|
|
49
|
-
def bins
|
91
|
+
def bins(&block)
|
50
92
|
@bins ||= Item.new
|
93
|
+
if block_given?
|
94
|
+
@bins.callback(&block)
|
95
|
+
end
|
96
|
+
@bins
|
51
97
|
end
|
52
98
|
|
53
|
-
def softbins
|
99
|
+
def softbins(&block)
|
54
100
|
@softbins ||= Item.new
|
55
101
|
if block_given?
|
56
102
|
@softbins.callback(&block)
|
@@ -76,14 +122,19 @@ module TestIds
|
|
76
122
|
numbers.algorithm = val
|
77
123
|
end
|
78
124
|
|
125
|
+
def send_to_ate=(val)
|
126
|
+
@send_to_ate = val
|
127
|
+
end
|
128
|
+
|
129
|
+
def send_to_ate
|
130
|
+
@send_to_ate
|
131
|
+
end
|
132
|
+
|
79
133
|
def validate!
|
80
134
|
unless validated?
|
81
135
|
if bins.algorithm
|
82
136
|
fail 'The TestIds bins configuration cannot be set to an algorithm, only a range set by bins.include and bins.exclude is permitted'
|
83
137
|
end
|
84
|
-
if bins.callback
|
85
|
-
fail 'The TestIds bins configuration cannot be set by a callback, only a range set by bins.include and bins.exclude is permitted'
|
86
|
-
end
|
87
138
|
@validated = true
|
88
139
|
freeze
|
89
140
|
end
|
@@ -103,5 +154,20 @@ module TestIds
|
|
103
154
|
numbers.freeze
|
104
155
|
super
|
105
156
|
end
|
157
|
+
|
158
|
+
def to_json(*a)
|
159
|
+
{
|
160
|
+
'bins' => bins,
|
161
|
+
'softbins' => softbins,
|
162
|
+
'numbers' => numbers
|
163
|
+
}.to_json(*a)
|
164
|
+
end
|
165
|
+
|
166
|
+
# @api private
|
167
|
+
def load_from_serialized(store)
|
168
|
+
bins.load_from_serialized(store['bins'])
|
169
|
+
softbins.load_from_serialized(store['softbins'])
|
170
|
+
numbers.load_from_serialized(store['numbers'])
|
171
|
+
end
|
106
172
|
end
|
107
173
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'origen_testers/flow'
|
2
2
|
module OrigenTesters
|
3
3
|
module Flow
|
4
|
+
BIN_OPTS = [:bin, :softbin, :bin_size, :softbin_size, :number, :number_size]
|
5
|
+
|
4
6
|
# Override the flow.test method to inject our generated bin and
|
5
7
|
# test numbers
|
6
8
|
alias_method :_orig_test, :test
|
@@ -8,6 +10,13 @@ module OrigenTesters
|
|
8
10
|
if TestIds.configured? && options[:test_ids] != :notrack
|
9
11
|
TestIds.current_configuration.allocator.allocate(instance, options)
|
10
12
|
end
|
13
|
+
if TestIds.configured?
|
14
|
+
if TestIds.current_configuration.send_to_ate == false
|
15
|
+
BIN_OPTS.each do |opt|
|
16
|
+
options.delete(opt)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
11
20
|
_orig_test(instance, options)
|
12
21
|
end
|
13
22
|
end
|
data/lib/test_ids.rb
CHANGED
@@ -30,10 +30,17 @@ module TestIds
|
|
30
30
|
}
|
31
31
|
end
|
32
32
|
|
33
|
-
# Load an existing allocator, which will be loaded with
|
33
|
+
# Load an existing allocator, which will be loaded with a configuration based on what has
|
34
|
+
# been serialized into the database if present, otherwise it will have an empty configuration.
|
35
|
+
# Returns nil if the given database can not be found.
|
34
36
|
# @api internal
|
35
37
|
def load_allocator(id = nil)
|
36
|
-
|
38
|
+
f = TestIds.database_file(id)
|
39
|
+
if File.exist?(f)
|
40
|
+
a = Configuration.new(id).allocator
|
41
|
+
a.load_configuration_from_store
|
42
|
+
a
|
43
|
+
end
|
37
44
|
end
|
38
45
|
|
39
46
|
def current_configuration
|
@@ -159,6 +166,10 @@ module TestIds
|
|
159
166
|
@publish = val ? :save : :dont_save
|
160
167
|
end
|
161
168
|
|
169
|
+
def next_in_range(range, options)
|
170
|
+
current_configuration.allocator.next_in_range(range, options)
|
171
|
+
end
|
172
|
+
|
162
173
|
## When set to true, all numbers generated will be checked to see if they comply
|
163
174
|
## with the current configuration, and if not they will be re-assigned based on the
|
164
175
|
## current configuration
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: test_ids
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
4
|
+
version: 0.8.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stephen McGinty
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-05-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: origen
|
@@ -69,6 +69,7 @@ files:
|
|
69
69
|
- lib/test_ids/allocator.rb
|
70
70
|
- lib/test_ids/bin_array.rb
|
71
71
|
- lib/test_ids/commands/clear.rb
|
72
|
+
- lib/test_ids/commands/repair.rb
|
72
73
|
- lib/test_ids/configuration.rb
|
73
74
|
- lib/test_ids/git.rb
|
74
75
|
- lib/test_ids/origen/origen.rb
|