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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3187c134f83d0e5a9c3513027fb2c1bad26a1b12
4
- data.tar.gz: 8d265c7f97de0eb22d62efff091a0974fc5e0482
3
+ metadata.gz: 398ea9b7ec2c5bad27c399d18f0e94d55e505cdb
4
+ data.tar.gz: 7474a3c2854ad487f8433316c74098dddcae3f92
5
5
  SHA512:
6
- metadata.gz: f30d12207607a2eac1335af1f9e4e4e37854dbd36aea7647c79e74cb68e3c1810d7cdbe5d4174f2c52ac8e7081ad3b96119cb8f3386d314d4966672fac29b9ce
7
- data.tar.gz: 2d0347b9c85d6edec9f36be9994639916f1a6eb0447a9ab5bbc5f98b48b0f9d54d565e7aca366fa1edbea7f13f3a1ea41a530d717586b1e8bc1d911e143cf659
6
+ metadata.gz: 901efb539a198fa3ba56c50b948dd75a70217e5d7b03fb2c8febfa34e06e5cb69a4c3784e18c11c62677eda9b051c3c4f67b2609dcc071b166d64719ede8888f
7
+ data.tar.gz: 06cd20a662de6515f40674ef866ef5b6807e99658c9087b88dc3fa46b0943bc07f58bc5c6fdc66c0cb029a16a56fbfb89a68198fd1586068aa85f91eef1da738
@@ -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
@@ -1,7 +1,7 @@
1
1
  module TestIds
2
2
  MAJOR = 0
3
3
  MINOR = 8
4
- BUGFIX = 0
4
+ BUGFIX = 1
5
5
  DEV = nil
6
6
 
7
7
  VERSION = [MAJOR, MINOR, BUGFIX].join(".") + (DEV ? ".pre#{DEV}" : '')
@@ -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 = 1
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']['bin'][bin_id] ||= {}
48
- softbin = store['assigned']['softbin'][softbin_id] ||= {}
49
- number = store['assigned']['number'][number_id] ||= {}
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']['bin'][options[:bin].to_s] = true
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']['bin'][bin['number'].to_s]
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']['softbin'][options[:softbin].to_s] = true
76
- elsif store['manually_assigned']['softbin'][softbin['number'].to_s]
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']['number'][options[:number].to_s] = true
89
- elsif store['manually_assigned']['number'][number['number'].to_s]
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 config.softbins.algorithm && config.softbins.algorithm.to_s =~ /n/
100
- number['number'] ||= allocate_number(bin: bin['number'], size: number_size)
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']['bin'][(bin['number'] + i).to_s] = time if bin['number'] && options[:bin] != :none
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']['softbin'][(softbin['number'] + i).to_s] = time if softbin['number'] && options[:softbin] != :none
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']['number'][(number['number'] + i).to_s] = time if number['number'] && options[:number] != :none
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
- s = JSON.load(File.read(file)) if file && File.exist?(file)
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
- if s['format_revision'] != STORE_FORMAT_REVISION
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' => STORE_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
- @last_bin = s['pointers']['bin']
172
- @last_softbin = s['pointers']['softbin']
173
- @last_number = s['pointers']['number']
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
- 'assigned' => { 'bin' => {}, 'softbin' => {}, 'number' => {} },
179
- 'manually_assigned' => { 'bin' => {}, 'softbin' => {}, 'number' => {} },
180
- 'pointers' => { 'bin' => nil, 'softbin' => nil, 'number' => nil },
181
- 'references' => { 'bin' => {}, 'softbin' => {}, 'number' => {} }
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']['softbin'] = {}
191
- store['manually_assigned']['softbin'] = {}
192
- store['pointers']['softbin'] = nil
193
- store['references']['softbin'] = {}
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']['bin'] = {}
197
- store['manually_assigned']['bin'] = {}
198
- store['pointers']['bin'] = nil
199
- store['references']['bin'] = {}
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']['number'] = {}
203
- store['manually_assigned']['number'] = {}
204
- store['pointers']['number'] = nil
205
- store['references']['number'] = {}
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') { |f| f.puts JSON.pretty_generate(store) }
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
- return nil if config.bins.empty?
237
- if store['pointers']['bin'] == 'done'
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']['bin'][b.to_s] || config.bins.exclude.include?(b))
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']['bin'] = b
447
+ store['pointers']['bins'] = b
249
448
  else
250
- store['pointers']['bin'] = 'done'
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']['bin'] = store['references']['bin'].sort_by { |k, v| v }.to_h
456
+ store['references']['bins'] = store['references']['bins'].sort_by { |k, v| v }.to_h
258
457
  if options[:size] == 1
259
- store['references']['bin'].first[0].to_i
458
+ store['references']['bins'].first[0].to_i
260
459
  else
261
- reclaim(store['references']['bin'], options)
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']['softbin']
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']['softbin'] == 'done'
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']['softbin'][b.to_s] || config.softbins.exclude.include?(b))
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']['softbin'] = b
530
+ store['pointers']['softbins'] = b
332
531
  else
333
- store['pointers']['softbin'] = 'done'
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']['softbin'] = store['references']['softbin'].sort_by { |k, v| v }.to_h
540
+ store['references']['softbins'] = store['references']['softbins'].sort_by { |k, v| v }.to_h
342
541
  if options[:size] == 1
343
- store['references']['softbin'].first[0].to_i
542
+ store['references']['softbins'].first[0].to_i
344
543
  else
345
- reclaim(store['references']['softbin'], options)
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']['number']
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']['number'] == 'done'
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']['number'][b.to_s] || config.numbers.exclude.include?(b))
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']['number'] = b
614
+ store['pointers']['numbers'] = b
416
615
  else
417
- store['pointers']['number'] = 'done'
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']['number'] = store['references']['number'].sort_by { |k, v| v }.to_h
624
+ store['references']['numbers'] = store['references']['numbers'].sort_by { |k, v| v }.to_h
426
625
  if options[:size] == 1
427
- store['references']['number'].first[0].to_i
626
+ store['references']['numbers'].first[0].to_i
428
627
  else
429
- reclaim(store['references']['number'], options)
628
+ reclaim(store['references']['numbers'], options)
430
629
  end
431
630
  end
432
631
 
@@ -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
- # def compliant?(number)
29
- # end
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 an empty configuration
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
- Configuration.new(id).allocator
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.0
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: 2017-07-04 00:00:00.000000000 Z
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