test_ids 0.8.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|