test_ids 0.8.2 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/config/commands.rb +2 -0
- data/config/version.rb +3 -4
- data/lib/test_ids.rb +93 -17
- data/lib/test_ids/allocator.rb +224 -302
- data/lib/test_ids/configuration.rb +32 -14
- data/lib/test_ids/origen_testers/flow.rb +9 -6
- data/lib/test_ids_dev/interface.rb +9 -3
- data/program/prb1.rb +12 -5
- metadata +5 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c45579faaf12aa3964acd4caa219e9b3443c366d2186870a5ea86381376dbeae
|
4
|
+
data.tar.gz: 1cb178416379a578fa91c472ce7e4d22521a428bfc40d400ea52127b10dd1c8e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cc9951b84d9f7754d3225461157b6cd7579e6745c22ec8de0bb60ab91bc51d9f48ffdecf2c136329506fde707df173ba8e238233190e4236b62793beef92c06c
|
7
|
+
data.tar.gz: a04f02f1207e4599a3c0cb5d986b4e28741206afaf64e530b9d47dd86849b2c4a322e1a6c35e45284d727731d2bd592b4a5f4306b9eade6ccde0087062e01272
|
data/config/commands.rb
CHANGED
@@ -33,6 +33,8 @@ when "examples", "test"
|
|
33
33
|
# Program generator integration test
|
34
34
|
ARGV = %w(program/prb1.rb -t default -e default -r approved)
|
35
35
|
load "#{Origen.top}/lib/origen/commands/program.rb"
|
36
|
+
ARGV = %W(program/prb1.rb -t dut2 -o #{Origen.root}/output/dut2 -e default -r approved/dut2)
|
37
|
+
load "#{Origen.top}/lib/origen/commands/program.rb"
|
36
38
|
|
37
39
|
if Origen.app.stats.changed_files == 0 &&
|
38
40
|
Origen.app.stats.new_files == 0 &&
|
data/config/version.rb
CHANGED
data/lib/test_ids.rb
CHANGED
@@ -24,12 +24,58 @@ module TestIds
|
|
24
24
|
# returned will be the same as would be injected into flow.test.
|
25
25
|
def allocate(instance, options = {})
|
26
26
|
opts = options.dup
|
27
|
+
inject_flow_id(opts)
|
27
28
|
current_configuration.allocator.allocate(instance, opts)
|
28
29
|
{ bin: opts[:bin], bin_size: opts[:bin_size], softbin: opts[:softbin], softbin_size: opts[:softbin_size],
|
29
30
|
number: opts[:number], number_size: opts[:number_size]
|
30
31
|
}
|
31
32
|
end
|
32
33
|
|
34
|
+
# Similar to allocate, but allocates a test number only, i.e. no bin or softbin
|
35
|
+
def allocate_number(instance, options = {})
|
36
|
+
opts = options.dup
|
37
|
+
opts[:bin] = :none
|
38
|
+
opts[:softbin] = :none
|
39
|
+
inject_flow_id(opts)
|
40
|
+
current_configuration.allocator.allocate(instance, opts)
|
41
|
+
{
|
42
|
+
number: opts[:number], number_size: opts[:number_size]
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
# Similar to allocate, but allocates a softbin number only, i.e. no bin or test number
|
47
|
+
def allocate_softbin(instance, options = {})
|
48
|
+
opts = options.dup
|
49
|
+
opts[:bin] = :none
|
50
|
+
opts[:number] = :none
|
51
|
+
inject_flow_id(opts)
|
52
|
+
current_configuration.allocator.allocate(instance, opts)
|
53
|
+
{
|
54
|
+
softbin: opts[:softbin], softbin_size: opts[:softbin_size]
|
55
|
+
}
|
56
|
+
end
|
57
|
+
alias_method :allocate_soft_bin, :allocate_softbin
|
58
|
+
|
59
|
+
# Similar to allocate, but allocates a bin number only, i.e. no softbin or test number
|
60
|
+
def allocate_bin(instance, options = {})
|
61
|
+
opts = options.dup
|
62
|
+
opts[:softbin] = :none
|
63
|
+
opts[:number] = :none
|
64
|
+
inject_flow_id(opts)
|
65
|
+
current_configuration.allocator.allocate(instance, opts)
|
66
|
+
{
|
67
|
+
softbin: opts[:bin], softbin_size: opts[:bin_size]
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
# @api private
|
72
|
+
def inject_flow_id(options)
|
73
|
+
if Origen.interface_loaded?
|
74
|
+
flow = Origen.interface.flow
|
75
|
+
options[:test_ids_flow_id] = flow.try(:top_level).try(:id) || flow.id
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
33
79
|
# Load an existing allocator, which will be loaded with a configuration based on what has
|
34
80
|
# been serialized into the database if present, otherwise it will have an empty configuration.
|
35
81
|
# Returns nil if the given database can not be found.
|
@@ -47,9 +93,11 @@ module TestIds
|
|
47
93
|
configuration(@configuration_id)
|
48
94
|
end
|
49
95
|
|
50
|
-
def configuration(id)
|
96
|
+
def configuration(id, fail_on_missing = true)
|
51
97
|
return @configuration[id] if @configuration && @configuration[id]
|
52
|
-
|
98
|
+
if fail_on_missing
|
99
|
+
fail('You have to create the configuration first before you can access it')
|
100
|
+
end
|
53
101
|
end
|
54
102
|
alias_method :config, :configuration
|
55
103
|
|
@@ -73,26 +121,51 @@ module TestIds
|
|
73
121
|
initialize_git
|
74
122
|
end
|
75
123
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
124
|
+
# Switch the current configuration to the given ID
|
125
|
+
def config=(id)
|
126
|
+
unless @configuration[id]
|
127
|
+
fail "The TestIds configuration '#{id}' has not been defined yet!"
|
128
|
+
end
|
129
|
+
@configuration_id = id
|
130
|
+
end
|
81
131
|
|
82
|
-
#
|
132
|
+
# Return an Array of configuration IDs
|
133
|
+
def configs
|
134
|
+
@configuration.ids
|
135
|
+
end
|
83
136
|
|
84
|
-
|
137
|
+
def bin_config=(id)
|
138
|
+
@bin_config = id
|
139
|
+
end
|
85
140
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
# new.allocator.instance_variable_set('@config', new)
|
90
|
-
# @configuration[@configuration_id] = new
|
141
|
+
def bin_config
|
142
|
+
@bin_config ? configuration(@bin_config, false) : current_configuration
|
143
|
+
end
|
91
144
|
|
92
|
-
|
145
|
+
def softbin_config=(id)
|
146
|
+
@softbin_config = id
|
147
|
+
end
|
93
148
|
|
94
|
-
|
95
|
-
|
149
|
+
def softbin_config
|
150
|
+
@softbin_config ? configuration(@softbin_config, false) : current_configuration
|
151
|
+
end
|
152
|
+
|
153
|
+
def number_config=(id)
|
154
|
+
@number_config = id
|
155
|
+
end
|
156
|
+
|
157
|
+
def number_config
|
158
|
+
@number_config ? configuration(@number_config, false) : current_configuration
|
159
|
+
end
|
160
|
+
|
161
|
+
# Temporarily switches the current configuration to the given ID for the
|
162
|
+
# duration of the given block, then switches it back to what it was
|
163
|
+
def with_config(id)
|
164
|
+
orig = @configuration_id
|
165
|
+
@configuration_id = id
|
166
|
+
yield
|
167
|
+
@configuration_id = orig
|
168
|
+
end
|
96
169
|
|
97
170
|
def configured?
|
98
171
|
!!@configuration_id
|
@@ -205,6 +278,9 @@ module TestIds
|
|
205
278
|
|
206
279
|
def clear_configuration_id
|
207
280
|
@configuration_id = nil
|
281
|
+
@bin_config = nil
|
282
|
+
@softbin_config = nil
|
283
|
+
@number_config = nil
|
208
284
|
end
|
209
285
|
|
210
286
|
def testing=(val)
|
data/lib/test_ids/allocator.rb
CHANGED
@@ -8,202 +8,182 @@ module TestIds
|
|
8
8
|
class Allocator
|
9
9
|
STORE_FORMAT_REVISION = 2
|
10
10
|
|
11
|
-
attr_reader :config
|
12
|
-
|
13
11
|
def initialize(configuration)
|
14
12
|
@config = configuration
|
15
13
|
end
|
16
14
|
|
15
|
+
def config(type = nil)
|
16
|
+
if type
|
17
|
+
type = type.to_s
|
18
|
+
type.chop! if type[-1] == 's'
|
19
|
+
TestIds.send("#{type}_config") || @config
|
20
|
+
else
|
21
|
+
@config
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
17
25
|
# Allocates a softbin number from the range specified in the test flow
|
18
26
|
# It also keeps a track of the last softbin assigned out from a particular range
|
19
27
|
# and uses that to increment the pointers accordingly.
|
20
28
|
# If a numeric number is passed to the softbin, it uses that number.
|
21
29
|
# The configuration for the TestId plugin needs to pass in the bin number and the options from the test flow
|
22
30
|
# For this method to work as intended.
|
23
|
-
|
24
|
-
|
31
|
+
# This will handle the following range inputs:
|
32
|
+
# - Range, Ex: 0..10
|
33
|
+
# - Array, Ex: [0..10, 20..30]
|
34
|
+
def next_in_range(range_definition, options)
|
35
|
+
if range_definition.is_a?(Range)
|
36
|
+
range = range_definition.to_a
|
37
|
+
elsif range_definition.is_a?(Array)
|
38
|
+
range = []
|
39
|
+
range_definition.each do |range_element|
|
40
|
+
range += range_element.is_a?(Integer) ? [range_element] : range_element.step(options[:size]).to_a
|
41
|
+
end
|
42
|
+
if range.uniq.size != range.size
|
43
|
+
Origen.log.error "Duplicate or overlapping range has been detected in configuration: \'#{TestIds.current_configuration.id}\'."
|
44
|
+
fail
|
45
|
+
end
|
46
|
+
end
|
47
|
+
range_item(range, range_definition, options)
|
25
48
|
end
|
26
49
|
|
27
|
-
def range_item(range, options)
|
28
|
-
|
29
|
-
#
|
30
|
-
|
31
|
-
|
32
|
-
lines = File.readlines(file)
|
33
|
-
# Remove any header comment lines since these are not valid JSON
|
34
|
-
lines.shift while lines.first =~ /^\/\// && !lines.empty?
|
35
|
-
s = JSON.load(lines.join("\n"))
|
36
|
-
rangehash = s['pointers']['ranges']
|
37
|
-
rangehash = Hash[rangehash.map { |k, v| [k.to_sym, v] }]
|
50
|
+
def range_item(range, range_definition, options)
|
51
|
+
# This is the actual fix, it should now not be dependent on the json file being read in, instead the store pointers
|
52
|
+
# will be utilized to get the correct number assigned from the range.
|
53
|
+
if store['pointers']['ranges'].nil?
|
54
|
+
rangehash = {}
|
38
55
|
else
|
39
|
-
|
40
|
-
rangehash =
|
56
|
+
rangehash = store['pointers']['ranges']
|
57
|
+
rangehash = Hash[rangehash.map { |k, v| [k.to_sym, v] }]
|
41
58
|
end
|
59
|
+
orig_options = options.dup
|
42
60
|
# Check the database to see if the passed in range has already been included in the database hash
|
43
|
-
if rangehash.key?(:"#{
|
61
|
+
if rangehash.key?(:"#{range_definition}")
|
44
62
|
# Read out the database hash to see what the last_softbin given out was for that range.
|
45
63
|
# This hash is updated whenever a new softbin is assigned, so it should have the updated values for each range.
|
46
|
-
previous_assigned_value = rangehash[:"#{
|
64
|
+
previous_assigned_value = rangehash[:"#{range_definition}"].to_i
|
47
65
|
# Now calculate the new pointer.
|
48
|
-
@pointer = previous_assigned_value
|
66
|
+
@pointer = range.index(previous_assigned_value) + 1
|
49
67
|
# Check if the last_softbin given out is the same as the range[@pointer],
|
50
68
|
# if so increment pointer by softbin size, default size is 1, config.softbins.size is configurable.
|
51
69
|
# from example above, pointer was calculated as 1,range[1] is 10101 and is same as last_softbin, so pointer is incremented
|
52
70
|
# and new value is assigned to the softbin.
|
53
|
-
if previous_assigned_value == range
|
71
|
+
if previous_assigned_value == range[@pointer]
|
54
72
|
@pointer += options[:size]
|
55
|
-
assigned_value = range
|
73
|
+
assigned_value = range[@pointer]
|
56
74
|
else
|
57
75
|
# Because of the pointer calculations above, I don't think it will ever reach here, has not in my test cases so far!
|
58
|
-
assigned_value = range
|
76
|
+
assigned_value = range[@pointer]
|
59
77
|
end
|
60
78
|
# Now update the database pointers to point to the lastest assigned softbin for a given range.
|
61
|
-
rangehash.merge!(:"#{
|
79
|
+
rangehash.merge!(:"#{range_definition}" => "#{range[@pointer]}")
|
62
80
|
else
|
63
81
|
# This is the case for a brand new range that has not been passed before
|
64
82
|
# We start from the first value as the assigned softbin and update the database to reflect.
|
65
83
|
@pointer = 0
|
66
|
-
rangehash.merge!(:"#{
|
67
|
-
assigned_value = range
|
84
|
+
rangehash.merge!(:"#{range_definition}" => "#{range[@pointer]}")
|
85
|
+
assigned_value = range[@pointer]
|
68
86
|
end
|
69
|
-
unless !assigned_value.nil? &&
|
87
|
+
unless !assigned_value.nil? && range.include?(assigned_value)
|
70
88
|
Origen.log.error 'Assigned value not in range'
|
71
89
|
fail
|
72
90
|
end
|
91
|
+
# Since the assigned value for this test has now changed, update store to contain the newly assigned value
|
92
|
+
# so that when the json file is written, it contains the latest assigned value name.
|
93
|
+
store['pointers']['ranges'] = rangehash
|
73
94
|
assigned_value
|
74
95
|
end
|
75
96
|
|
76
|
-
#
|
77
|
-
#
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
# should be reserved
|
87
|
-
if (options[:bin].is_a?(Symbol) || options[:bin].is_a?(String)) && options[:bin] != :none
|
88
|
-
bin_id = options[:bin].to_s
|
97
|
+
# Returns an array containing :bin, :softbin, :number in the order that they should be calculated in order to fulfil
|
98
|
+
# the requirements of the current configuration and the given options.
|
99
|
+
# If an item is not required (e.g. if set to :none in the options), then it will not be present in the array.
|
100
|
+
def allocation_order(options)
|
101
|
+
items = []
|
102
|
+
items_required = 0
|
103
|
+
if allocation_required?(:bin, options) ||
|
104
|
+
(allocation_required?(:softbin, options) && config(:softbin).softbins.needs?(:bin)) ||
|
105
|
+
(allocation_required?(:number, options) && config(:number).numbers.needs?(:bin))
|
106
|
+
items_required += 1
|
89
107
|
else
|
90
|
-
|
108
|
+
bin_done = true
|
91
109
|
end
|
92
|
-
if (
|
93
|
-
|
110
|
+
if allocation_required?(:softbin, options) ||
|
111
|
+
(allocation_required?(:bin, options) && config(:bin).bins.needs?(:softbin)) ||
|
112
|
+
(allocation_required?(:number, options) && config(:number).numbers.needs?(:softbin))
|
113
|
+
items_required += 1
|
94
114
|
else
|
95
|
-
|
115
|
+
softbin_done = true
|
96
116
|
end
|
97
|
-
if (
|
98
|
-
|
117
|
+
if allocation_required?(:number, options) ||
|
118
|
+
(allocation_required?(:bin, options) && config(:bin).bins.needs?(:number)) ||
|
119
|
+
(allocation_required?(:softbin, options) && config(:softbin).softbins.needs?(:number))
|
120
|
+
items_required += 1
|
99
121
|
else
|
100
|
-
|
101
|
-
end
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
bin['number'] = options[:bin]
|
115
|
-
bin['size'] = bin_size
|
116
|
-
store['manually_assigned']['bins'][options[:bin].to_s] = true
|
117
|
-
# Regenerate the bin if the original allocation has since been applied
|
118
|
-
# manually elsewhere
|
119
|
-
elsif store['manually_assigned']['bins'][bin['number'].to_s]
|
120
|
-
bin['number'] = nil
|
121
|
-
bin['size'] = nil
|
122
|
-
# Also regenerate these as they could be a function of the bin
|
123
|
-
if config.softbins.function?
|
124
|
-
softbin['number'] = nil
|
125
|
-
softbin['size'] = nil
|
126
|
-
end
|
127
|
-
if config.numbers.function?
|
128
|
-
number['number'] = nil
|
129
|
-
number['size'] = nil
|
130
|
-
end
|
131
|
-
end
|
132
|
-
if options[:softbin] && options[:softbin].is_a?(Numeric)
|
133
|
-
softbin['number'] = options[:softbin]
|
134
|
-
softbin['size'] = softbin_size
|
135
|
-
store['manually_assigned']['softbins'][options[:softbin].to_s] = true
|
136
|
-
elsif store['manually_assigned']['softbins'][softbin['number'].to_s]
|
137
|
-
softbin['number'] = nil
|
138
|
-
softbin['size'] = nil
|
139
|
-
# Also regenerate the number as it could be a function of the softbin
|
140
|
-
if config.numbers.function?
|
141
|
-
number['number'] = nil
|
142
|
-
number['size'] = nil
|
143
|
-
end
|
144
|
-
end
|
145
|
-
if options[:number] && options[:number].is_a?(Numeric)
|
146
|
-
number['number'] = options[:number]
|
147
|
-
number['size'] = number_size
|
148
|
-
store['manually_assigned']['numbers'][options[:number].to_s] = true
|
149
|
-
elsif store['manually_assigned']['numbers'][number['number'].to_s]
|
150
|
-
number['number'] = nil
|
151
|
-
number['size'] = nil
|
152
|
-
# Also regenerate the softbin as it could be a function of the number
|
153
|
-
if config.softbins.function?
|
154
|
-
softbin['number'] = nil
|
155
|
-
softbin['size'] = nil
|
122
|
+
number_done = true
|
123
|
+
end
|
124
|
+
items_required.times do |i|
|
125
|
+
if !bin_done && (!config(:bin).bins.needs?(:softbin) || softbin_done) && (!config(:bin).bins.needs?(:number) || number_done)
|
126
|
+
items << :bin
|
127
|
+
bin_done = true
|
128
|
+
elsif !softbin_done && (!config(:softbin).softbins.needs?(:bin) || bin_done) && (!config(:softbin).softbins.needs?(:number) || number_done)
|
129
|
+
items << :softbin
|
130
|
+
softbin_done = true
|
131
|
+
elsif !number_done && (!config(:number).numbers.needs?(:bin) || bin_done) && (!config(:number).numbers.needs?(:softbin) || softbin_done)
|
132
|
+
items << :number
|
133
|
+
number_done = true
|
134
|
+
else
|
135
|
+
fail "Couldn't work out whether to generate next on iteration #{i} of #{items_required}, already picked: #{items}"
|
156
136
|
end
|
157
137
|
end
|
138
|
+
items
|
139
|
+
end
|
158
140
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
if (config.softbins.algorithm && config.softbins.algorithm.to_s =~ /n/) ||
|
166
|
-
(config.softbins.callback && !config.numbers.function?)
|
167
|
-
number['number'] ||= allocate_number(options.merge(bin: bin['number'], size: number_size))
|
168
|
-
number['size'] ||= number_size
|
169
|
-
softbin['number'] ||= allocate_softbin(options.merge(bin: bin['number'], number: number['number'], size: softbin_size))
|
170
|
-
softbin['size'] ||= softbin_size
|
171
|
-
else
|
172
|
-
softbin['number'] ||= allocate_softbin(options.merge(bin: bin['number'], size: softbin_size))
|
173
|
-
softbin['size'] ||= softbin_size
|
174
|
-
number['number'] ||= allocate_number(options.merge(bin: bin['number'], softbin: softbin['number'], size: number_size))
|
175
|
-
number['size'] ||= number_size
|
176
|
-
end
|
141
|
+
# Main method to inject generated bin and test numbers, the given
|
142
|
+
# options instance is modified accordingly
|
143
|
+
def allocate(instance, options)
|
144
|
+
orig_options = options.dup
|
145
|
+
clean(options)
|
146
|
+
name = extract_test_name(instance, options)
|
177
147
|
|
178
|
-
|
179
|
-
time = Time.now.to_f
|
180
|
-
bin_size.times do |i|
|
181
|
-
store['references']['bins'][(bin['number'] + i).to_s] = time if bin['number'] && options[:bin] != :none
|
182
|
-
end
|
183
|
-
softbin_size.times do |i|
|
184
|
-
store['references']['softbins'][(softbin['number'] + i).to_s] = time if softbin['number'] && options[:softbin] != :none
|
185
|
-
end
|
186
|
-
number_size.times do |i|
|
187
|
-
store['references']['numbers'][(number['number'] + i).to_s] = time if number['number'] && options[:number] != :none
|
188
|
-
end
|
148
|
+
nones = []
|
189
149
|
|
190
|
-
#
|
191
|
-
|
192
|
-
options[
|
193
|
-
|
150
|
+
# Record any :nones that are present for later
|
151
|
+
[:bin, :softbin, :number].each do |type|
|
152
|
+
nones << type if options[type] == :none
|
153
|
+
config(type).allocator.instance_variable_set('@needs_regenerated', {})
|
194
154
|
end
|
195
|
-
|
196
|
-
|
197
|
-
|
155
|
+
|
156
|
+
allocation_order(options).each do |type|
|
157
|
+
config(type).allocator.send(:_allocate, type, name, options)
|
198
158
|
end
|
199
|
-
|
200
|
-
|
201
|
-
|
159
|
+
|
160
|
+
# Turn any :nones into nils in the returned options
|
161
|
+
nones.each do |type|
|
162
|
+
options[type] = nil
|
163
|
+
options["#{type}_size"] = nil
|
202
164
|
end
|
203
165
|
|
204
166
|
options
|
205
167
|
end
|
206
168
|
|
169
|
+
# Merge the given other store into the current one, it is assumed that both are formatted
|
170
|
+
# from the same (latest) revision
|
171
|
+
def merge_store(other_store)
|
172
|
+
store['pointers'] = store['pointers'].merge(other_store['pointers'])
|
173
|
+
@last_bin = store['pointers']['bins']
|
174
|
+
@last_softbin = store['pointers']['softbins']
|
175
|
+
@last_number = store['pointers']['numbers']
|
176
|
+
store['assigned']['bins'] = store['assigned']['bins'].merge(other_store['assigned']['bins'])
|
177
|
+
store['assigned']['softbins'] = store['assigned']['softbins'].merge(other_store['assigned']['softbins'])
|
178
|
+
store['assigned']['numbers'] = store['assigned']['numbers'].merge(other_store['assigned']['numbers'])
|
179
|
+
store['manually_assigned']['bins'] = store['manually_assigned']['bins'].merge(other_store['manually_assigned']['bins'])
|
180
|
+
store['manually_assigned']['softbins'] = store['manually_assigned']['softbins'].merge(other_store['manually_assigned']['softbins'])
|
181
|
+
store['manually_assigned']['numbers'] = store['manually_assigned']['numbers'].merge(other_store['manually_assigned']['numbers'])
|
182
|
+
store['references']['bins'] = store['references']['bins'].merge(other_store['references']['bins'])
|
183
|
+
store['references']['softbins'] = store['references']['softbins'].merge(other_store['references']['softbins'])
|
184
|
+
store['references']['numbers'] = store['references']['numbers'].merge(other_store['references']['numbers'])
|
185
|
+
end
|
186
|
+
|
207
187
|
def store
|
208
188
|
@store ||= begin
|
209
189
|
if file && File.exist?(file)
|
@@ -267,7 +247,7 @@ module TestIds
|
|
267
247
|
{ 'bins' => 'bins', 'softbins' => 'softbins', 'numbers' => 'test_numbers' }.each do |type, name|
|
268
248
|
if !config.send(type).function? && store['pointers'][type] == 'done'
|
269
249
|
Origen.log.info "Checking for missing #{name}..."
|
270
|
-
recovered = add_missing_references(config.send
|
250
|
+
recovered = add_missing_references(config.send, store['references'][type])
|
271
251
|
if recovered == 0
|
272
252
|
Origen.log.info " All #{name} are already available."
|
273
253
|
else
|
@@ -396,6 +376,72 @@ module TestIds
|
|
396
376
|
|
397
377
|
private
|
398
378
|
|
379
|
+
def _allocate(type, name, options)
|
380
|
+
type_plural = "#{type}s"
|
381
|
+
conf = config.send(type_plural)
|
382
|
+
|
383
|
+
# First work out the test ID to be used for each of the numbers, and how many numbers
|
384
|
+
# should be reserved
|
385
|
+
if (options[type].is_a?(Symbol) || options[type].is_a?(String)) && options[type] != :none
|
386
|
+
id = options[type].to_s
|
387
|
+
else
|
388
|
+
id = name
|
389
|
+
end
|
390
|
+
id = "#{id}_#{options[:index]}" if options[:index]
|
391
|
+
id = "#{id}_#{options[:test_ids_flow_id]}" if config.unique_by_flow?
|
392
|
+
|
393
|
+
val = store['assigned'][type_plural][id] ||= {}
|
394
|
+
|
395
|
+
if options[type].is_a?(Integer)
|
396
|
+
unless val['number'] == options[type]
|
397
|
+
store['manually_assigned']["#{type}s"][options[type].to_s] = true
|
398
|
+
val['number'] = options[type]
|
399
|
+
end
|
400
|
+
else
|
401
|
+
# Will be set if an upstream dependent type has been marked for regeneration by the code below
|
402
|
+
if @needs_regenerated[type]
|
403
|
+
val['number'] = nil
|
404
|
+
val['size'] = nil
|
405
|
+
# Regenerate the number if the original allocation has since been applied manually elsewhere
|
406
|
+
elsif store['manually_assigned'][type_plural][val['number'].to_s]
|
407
|
+
val['number'] = nil
|
408
|
+
val['size'] = nil
|
409
|
+
# Also regenerate these as they could be a function of the number we just invalidated
|
410
|
+
([:bin, :softbin, :number] - [type]).each do |t|
|
411
|
+
if config.send("#{t}s").needs?(type)
|
412
|
+
@needs_regenerated[t] = true
|
413
|
+
end
|
414
|
+
end
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
if size = options["#{type}_size".to_sym]
|
419
|
+
val['size'] = size
|
420
|
+
end
|
421
|
+
|
422
|
+
# Generate the missing ones
|
423
|
+
val['size'] ||= conf.size
|
424
|
+
val['number'] ||= allocate_item(type, options.merge(size: val['size']))
|
425
|
+
|
426
|
+
# Record that there has been a reference to the final numbers
|
427
|
+
time = Time.now.to_f
|
428
|
+
val['size'].times do |i|
|
429
|
+
store['references'][type_plural][(val['number'] + i).to_s] = time if val['number'] && options[type] != :none
|
430
|
+
end
|
431
|
+
|
432
|
+
# Update the supplied options hash that will be forwarded to the program generator
|
433
|
+
options[type] = val['number']
|
434
|
+
options["#{type}_size".to_sym] = val['size']
|
435
|
+
end
|
436
|
+
|
437
|
+
def allocation_required?(type, options)
|
438
|
+
if options[type] == :none
|
439
|
+
false
|
440
|
+
else
|
441
|
+
!config(type).send("#{type}s").empty?
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
399
445
|
def remove_invalid_references(config_item, references, manually_assigned)
|
400
446
|
removed = 0
|
401
447
|
references.each do |num, time|
|
@@ -435,161 +481,34 @@ module TestIds
|
|
435
481
|
recovered
|
436
482
|
end
|
437
483
|
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
return nil if config.bins.empty? && !config.bins.callback
|
445
|
-
if store['pointers']['bins'] == 'done'
|
446
|
-
reclaim_bin(options)
|
447
|
-
elsif callback = config.bins.callback
|
448
|
-
callback.call(options)
|
449
|
-
else
|
450
|
-
b = config.bins.include.next(after: @last_bin, size: options[:size])
|
451
|
-
@last_bin = nil
|
452
|
-
while b && (store['manually_assigned']['bins'][b.to_s] || config.bins.exclude.include?(b))
|
453
|
-
b = config.bins.include.next(size: options[:size])
|
454
|
-
end
|
455
|
-
# When no bin is returned it means we have used them all, all future generation
|
456
|
-
# now switches to reclaim mode
|
457
|
-
if b
|
458
|
-
store['pointers']['bins'] = b
|
459
|
-
else
|
460
|
-
store['pointers']['bins'] = 'done'
|
461
|
-
reclaim_bin(options)
|
462
|
-
end
|
463
|
-
end
|
464
|
-
end
|
465
|
-
|
466
|
-
def reclaim_bin(options)
|
467
|
-
store['references']['bins'] = store['references']['bins'].sort_by { |k, v| v }.to_h
|
468
|
-
if options[:size] == 1
|
469
|
-
store['references']['bins'].first[0].to_i
|
470
|
-
else
|
471
|
-
reclaim(store['references']['bins'], options)
|
472
|
-
end
|
473
|
-
end
|
474
|
-
|
475
|
-
def allocate_softbin(options)
|
476
|
-
bin = options[:bin]
|
477
|
-
num = options[:number]
|
478
|
-
return nil if config.softbins.empty?
|
479
|
-
if config.softbins.algorithm
|
480
|
-
algo = config.softbins.algorithm.to_s.downcase
|
481
|
-
if algo.to_s =~ /^[b\dxn]+$/
|
484
|
+
def allocate_item(type, options)
|
485
|
+
type_plural = "#{type}s"
|
486
|
+
conf = config.send(type_plural)
|
487
|
+
if conf.algorithm
|
488
|
+
algo = conf.algorithm.to_s.downcase
|
489
|
+
if algo.to_s =~ /^[bsn\dx]+$/
|
482
490
|
number = algo.to_s
|
483
|
-
bin
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
number = number.sub(/b+/, bin.rjust(max_bin_size, '0'))
|
490
|
-
end
|
491
|
-
if number =~ /(n+)/
|
492
|
-
num = num.to_s
|
493
|
-
max_num_size = Regexp.last_match(1).size
|
494
|
-
if num.size > max_num_size
|
495
|
-
fail "Test number (#{num}) overflows the softbin number algorithm (#{algo})"
|
496
|
-
end
|
497
|
-
number = number.sub(/n+/, num.rjust(max_num_size, '0'))
|
498
|
-
end
|
499
|
-
if number =~ /(x+)/
|
500
|
-
max_counter_size = Regexp.last_match(1).size
|
501
|
-
refs = store['references']['softbins']
|
502
|
-
i = 0
|
503
|
-
possible = []
|
504
|
-
proposal = number.sub(/x+/, i.to_s.rjust(max_counter_size, '0'))
|
505
|
-
possible << proposal
|
506
|
-
while refs[proposal] && i.to_s.size <= max_counter_size
|
507
|
-
i += 1
|
508
|
-
proposal = number.sub(/x+/, i.to_s.rjust(max_counter_size, '0'))
|
509
|
-
possible << proposal
|
510
|
-
end
|
511
|
-
# Overflowed, need to go search for the oldest duplicate now
|
512
|
-
if i.to_s.size > max_counter_size
|
513
|
-
i = 0
|
514
|
-
# Not the most efficient search algorithm, but this should be hit very rarely
|
515
|
-
# and even then only to generate the bin the first time around
|
516
|
-
p = refs.sort_by { |bin, last_used| last_used }.find do |bin, last_used|
|
517
|
-
possible.include?(bin)
|
491
|
+
([:bin, :softbin, :number] - [type]).each do |t|
|
492
|
+
if number =~ /(#{t.to_s[0]}+)/
|
493
|
+
max_size = Regexp.last_match(1).size
|
494
|
+
num = options[t].to_s
|
495
|
+
if num.size > max_size
|
496
|
+
fail "The allocated number, #{num}, overflows the #{t} field in the #{type} algorithm - #{algo}"
|
518
497
|
end
|
519
|
-
|
498
|
+
number = number.sub(/#{t.to_s[0]}+/, num.rjust(max_size, '0'))
|
520
499
|
end
|
521
|
-
number = proposal
|
522
500
|
end
|
523
|
-
else
|
524
|
-
fail "Unknown softbin algorithm: #{algo}"
|
525
|
-
end
|
526
|
-
number.to_i
|
527
|
-
elsif callback = config.softbins.callback
|
528
|
-
callback.call(bin, options)
|
529
|
-
else
|
530
|
-
if store['pointers']['softbins'] == 'done'
|
531
|
-
reclaim_softbin(options)
|
532
|
-
else
|
533
|
-
b = config.softbins.include.next(after: @last_softbin, size: options[:size])
|
534
|
-
@last_softbin = nil
|
535
|
-
while b && (store['manually_assigned']['softbins'][b.to_s] || config.softbins.exclude.include?(b))
|
536
|
-
b = config.softbins.include.next(size: options[:size])
|
537
|
-
end
|
538
|
-
# When no softbin is returned it means we have used them all, all future generation
|
539
|
-
# now switches to reclaim mode
|
540
|
-
if b
|
541
|
-
store['pointers']['softbins'] = b
|
542
|
-
else
|
543
|
-
store['pointers']['softbins'] = 'done'
|
544
|
-
reclaim_softbin(options)
|
545
|
-
end
|
546
|
-
end
|
547
|
-
end
|
548
|
-
end
|
549
|
-
|
550
|
-
def reclaim_softbin(options)
|
551
|
-
store['references']['softbins'] = store['references']['softbins'].sort_by { |k, v| v }.to_h
|
552
|
-
if options[:size] == 1
|
553
|
-
store['references']['softbins'].first[0].to_i
|
554
|
-
else
|
555
|
-
reclaim(store['references']['softbins'], options)
|
556
|
-
end
|
557
|
-
end
|
558
501
|
|
559
|
-
def allocate_number(options)
|
560
|
-
bin = options[:bin]
|
561
|
-
softbin = options[:softbin]
|
562
|
-
return nil if config.numbers.empty?
|
563
|
-
if config.numbers.algorithm
|
564
|
-
algo = config.numbers.algorithm.to_s.downcase
|
565
|
-
if algo.to_s =~ /^[bs\dx]+$/
|
566
|
-
number = algo.to_s
|
567
|
-
bin = bin.to_s
|
568
|
-
if number =~ /(b+)/
|
569
|
-
max_bin_size = Regexp.last_match(1).size
|
570
|
-
if bin.size > max_bin_size
|
571
|
-
fail "Bin number (#{bin}) overflows the test number algorithm (#{algo})"
|
572
|
-
end
|
573
|
-
number = number.sub(/b+/, bin.rjust(max_bin_size, '0'))
|
574
|
-
end
|
575
|
-
softbin = softbin.to_s
|
576
|
-
if number =~ /(s+)/
|
577
|
-
max_softbin_size = Regexp.last_match(1).size
|
578
|
-
if softbin.size > max_softbin_size
|
579
|
-
fail "Softbin number (#{softbin}) overflows the test number algorithm (#{algo})"
|
580
|
-
end
|
581
|
-
number = number.sub(/s+/, softbin.rjust(max_softbin_size, '0'))
|
582
|
-
end
|
583
502
|
if number =~ /(x+)/
|
584
503
|
max_counter_size = Regexp.last_match(1).size
|
585
|
-
refs = store['references'][
|
504
|
+
refs = store['references'][type_plural]
|
586
505
|
i = 0
|
587
506
|
possible = []
|
588
|
-
proposal = number.sub(/x+/, i.to_s.rjust(max_counter_size, '0'))
|
507
|
+
proposal = number.sub(/x+/, i.to_s.rjust(max_counter_size, '0')).to_i.to_s
|
589
508
|
possible << proposal
|
590
509
|
while refs[proposal] && i.to_s.size <= max_counter_size
|
591
510
|
i += 1
|
592
|
-
proposal = number.sub(/x+/, i.to_s.rjust(max_counter_size, '0'))
|
511
|
+
proposal = number.sub(/x+/, i.to_s.rjust(max_counter_size, '0')).to_i.to_s
|
593
512
|
possible << proposal
|
594
513
|
end
|
595
514
|
# Overflowed, need to go search for the oldest duplicate now
|
@@ -604,39 +523,42 @@ module TestIds
|
|
604
523
|
end
|
605
524
|
number = proposal
|
606
525
|
end
|
607
|
-
number.to_i
|
608
526
|
else
|
609
|
-
fail "
|
527
|
+
fail "Illegal algorithm: #{algo}"
|
610
528
|
end
|
611
|
-
|
612
|
-
|
529
|
+
number.to_i
|
530
|
+
elsif callback = conf.callback
|
531
|
+
callback.call(options)
|
613
532
|
else
|
614
|
-
if store['pointers'][
|
615
|
-
|
533
|
+
if store['pointers'][type_plural] == 'done'
|
534
|
+
reclaim_item(type, options)
|
616
535
|
else
|
617
|
-
b =
|
618
|
-
@
|
619
|
-
while b && (store['manually_assigned'][
|
620
|
-
b =
|
536
|
+
b = conf.include.next(after: instance_variable_get("@last_#{type}"), size: options[:size])
|
537
|
+
instance_variable_set("@last_#{type}", nil)
|
538
|
+
while b && (store['manually_assigned'][type_plural][b.to_s] || conf.exclude.include?(b))
|
539
|
+
b = conf.include.next(size: options[:size])
|
621
540
|
end
|
622
541
|
# When no number is returned it means we have used them all, all future generation
|
623
542
|
# now switches to reclaim mode
|
624
543
|
if b
|
625
|
-
store['pointers'][
|
544
|
+
store['pointers'][type_plural] = b + (options[:size] || 1) - 1
|
545
|
+
b
|
626
546
|
else
|
627
|
-
store['pointers'][
|
628
|
-
|
547
|
+
store['pointers'][type_plural] = 'done'
|
548
|
+
reclaim_item(type, options)
|
629
549
|
end
|
630
550
|
end
|
631
551
|
end
|
632
552
|
end
|
633
553
|
|
634
|
-
def
|
635
|
-
|
554
|
+
def reclaim_item(type, options)
|
555
|
+
type_plural = "#{type}s"
|
556
|
+
store['references'][type_plural] = store['references'][type_plural].sort_by { |k, v| v }.to_h
|
636
557
|
if options[:size] == 1
|
637
|
-
store['references'][
|
558
|
+
v = store['references'][type_plural].first
|
559
|
+
v[0].to_i if v
|
638
560
|
else
|
639
|
-
reclaim(store['references'][
|
561
|
+
reclaim(store['references'][type_plural], options)
|
640
562
|
end
|
641
563
|
end
|
642
564
|
|
@@ -1,22 +1,29 @@
|
|
1
1
|
module TestIds
|
2
2
|
class Configuration
|
3
3
|
class Item
|
4
|
-
attr_accessor :include, :exclude, :algorithm, :size
|
4
|
+
attr_accessor :include, :exclude, :algorithm, :size, :needs
|
5
5
|
|
6
6
|
def initialize
|
7
7
|
@include = BinArray.new
|
8
8
|
@exclude = BinArray.new
|
9
|
+
@needs = []
|
9
10
|
@size = 1
|
10
11
|
end
|
11
12
|
|
12
|
-
def callback(&block)
|
13
|
+
def callback(options = {}, &block)
|
13
14
|
if block_given?
|
15
|
+
@needs += Array(options[:needs])
|
14
16
|
@callback = block
|
15
17
|
else
|
16
18
|
@callback
|
17
19
|
end
|
18
20
|
end
|
19
21
|
|
22
|
+
def needs?(type)
|
23
|
+
!!(!empty? && function? && (needs.include?(type) ||
|
24
|
+
(algorithm && (algorithm.to_s =~ /#{type.to_s[0]}/i))))
|
25
|
+
end
|
26
|
+
|
20
27
|
def empty?
|
21
28
|
include.empty? && exclude.empty? && !algorithm && !callback
|
22
29
|
end
|
@@ -36,6 +43,7 @@ module TestIds
|
|
36
43
|
def freeze
|
37
44
|
@include.freeze
|
38
45
|
@exclude.freeze
|
46
|
+
@needs.freeze
|
39
47
|
super
|
40
48
|
end
|
41
49
|
|
@@ -88,18 +96,23 @@ module TestIds
|
|
88
96
|
@id
|
89
97
|
end
|
90
98
|
|
91
|
-
def bins(&block)
|
99
|
+
def bins(options = {}, &block)
|
92
100
|
@bins ||= Item.new
|
93
101
|
if block_given?
|
94
|
-
@bins.callback(&block)
|
102
|
+
@bins.callback(options, &block)
|
95
103
|
end
|
96
104
|
@bins
|
97
105
|
end
|
98
106
|
|
99
|
-
|
107
|
+
# An alias for config.bins.algorithm=
|
108
|
+
def bins=(val)
|
109
|
+
bins.algorithm = val
|
110
|
+
end
|
111
|
+
|
112
|
+
def softbins(options = {}, &block)
|
100
113
|
@softbins ||= Item.new
|
101
114
|
if block_given?
|
102
|
-
@softbins.callback(&block)
|
115
|
+
@softbins.callback(options, &block)
|
103
116
|
end
|
104
117
|
@softbins
|
105
118
|
end
|
@@ -109,10 +122,10 @@ module TestIds
|
|
109
122
|
softbins.algorithm = val
|
110
123
|
end
|
111
124
|
|
112
|
-
def numbers(&block)
|
125
|
+
def numbers(options = {}, &block)
|
113
126
|
@numbers ||= Item.new
|
114
127
|
if block_given?
|
115
|
-
@numbers.callback(&block)
|
128
|
+
@numbers.callback(options, &block)
|
116
129
|
end
|
117
130
|
@numbers
|
118
131
|
end
|
@@ -123,18 +136,23 @@ module TestIds
|
|
123
136
|
end
|
124
137
|
|
125
138
|
def send_to_ate=(val)
|
126
|
-
@send_to_ate = val
|
139
|
+
@send_to_ate = !!val
|
127
140
|
end
|
128
141
|
|
129
|
-
def send_to_ate
|
130
|
-
@send_to_ate
|
142
|
+
def send_to_ate?
|
143
|
+
defined?(@send_to_ate) ? @send_to_ate : true
|
144
|
+
end
|
145
|
+
|
146
|
+
def unique_by_flow=(val)
|
147
|
+
@unique_by_flow = !!val
|
148
|
+
end
|
149
|
+
|
150
|
+
def unique_by_flow?
|
151
|
+
@unique_by_flow || false
|
131
152
|
end
|
132
153
|
|
133
154
|
def validate!
|
134
155
|
unless validated?
|
135
|
-
if bins.algorithm
|
136
|
-
fail 'The TestIds bins configuration cannot be set to an algorithm, only a range set by bins.include and bins.exclude is permitted'
|
137
|
-
end
|
138
156
|
@validated = true
|
139
157
|
freeze
|
140
158
|
end
|
@@ -7,13 +7,16 @@ module OrigenTesters
|
|
7
7
|
# test numbers
|
8
8
|
alias_method :_orig_test, :test
|
9
9
|
def test(instance, options = {})
|
10
|
-
if TestIds.configured? && options[:test_ids] != :notrack
|
11
|
-
TestIds.current_configuration.allocator.allocate(instance, options)
|
12
|
-
end
|
13
10
|
if TestIds.configured?
|
14
|
-
|
15
|
-
|
16
|
-
|
11
|
+
unless options[:test_ids] == :notrack
|
12
|
+
options[:test_ids_flow_id] = try(:top_level).try(:id) || id
|
13
|
+
|
14
|
+
TestIds.current_configuration.allocator.allocate(instance, options)
|
15
|
+
|
16
|
+
unless TestIds.current_configuration.send_to_ate?
|
17
|
+
BIN_OPTS.each do |opt|
|
18
|
+
options.delete(opt)
|
19
|
+
end
|
17
20
|
end
|
18
21
|
end
|
19
22
|
end
|
@@ -5,17 +5,23 @@ module TestIdsDev
|
|
5
5
|
def initialize(options = {})
|
6
6
|
case dut.test_ids
|
7
7
|
when 1
|
8
|
-
TestIds.configure do |config|
|
8
|
+
TestIds.configure id: :cfg1 do |config|
|
9
9
|
# Example of testing remote repo
|
10
10
|
# config.repo = 'ssh://git@sw-stash.freescale.net/~r49409/test_ids_repo.git'
|
11
11
|
config.bins.include << 3
|
12
12
|
config.bins.include << (10..20)
|
13
13
|
config.bins.exclude << 15
|
14
14
|
config.softbins = :bbbxx
|
15
|
-
config.numbers do |
|
16
|
-
softbin * 100
|
15
|
+
config.numbers needs: :softbin do |options|
|
16
|
+
options[:softbin] * 100
|
17
17
|
end
|
18
18
|
end
|
19
|
+
|
20
|
+
when 2
|
21
|
+
TestIds.configure id: :cfg2 do |config|
|
22
|
+
config.bins.include << (5..16)
|
23
|
+
config.softbins = :bbxxx
|
24
|
+
end
|
19
25
|
end
|
20
26
|
end
|
21
27
|
|
data/program/prb1.rb
CHANGED
@@ -1,7 +1,14 @@
|
|
1
1
|
Flow.create do
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
2
|
+
if dut.test_ids == 2
|
3
|
+
func :t1, bin: 11
|
4
|
+
func :t2, bin: 11
|
5
|
+
func :t3, bin: 11
|
6
|
+
func :t4, bin: 11
|
7
|
+
func :t5, bin: 11
|
8
|
+
else
|
9
|
+
func :t1
|
10
|
+
func :t2
|
11
|
+
func :t3
|
12
|
+
func :t3, bin: :none, sbin: :none
|
13
|
+
end
|
7
14
|
end
|
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:
|
4
|
+
version: 1.2.0
|
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: 2020-11-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: origen
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.
|
19
|
+
version: 0.57.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.
|
26
|
+
version: 0.57.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: origen_testers
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -100,8 +100,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
100
100
|
- !ruby/object:Gem::Version
|
101
101
|
version: 1.8.11
|
102
102
|
requirements: []
|
103
|
-
|
104
|
-
rubygems_version: 2.6.7
|
103
|
+
rubygems_version: 3.0.3
|
105
104
|
signing_key:
|
106
105
|
specification_version: 4
|
107
106
|
summary: Origen plugin to assign and track test program bins and test numbers
|