test_ids 0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e75b85981fb273db9e57af616ed8077d13f0c6d8
4
+ data.tar.gz: 1a1c23efe34764a6db9def529358430eeb7d8e29
5
+ SHA512:
6
+ metadata.gz: 444daec69e397bf06c6f0bd5982d956c1caf30b2bf2be07b0a37d4ec492a472285657775e8098194d63ccd767a2194ca5fbfc635ca39a885ffcf7589f41ebbfa
7
+ data.tar.gz: b98facddf7e176d90ddebb503221356c563102f63e2d5a48048be6a2118b5acf7a9349f7ac87d581421d98ce5c95d2b006a67a45c3fd23281ffa6cd1c7f38e63
@@ -0,0 +1,103 @@
1
+ require 'origen'
2
+ class TestIdsApplication < Origen::Application
3
+
4
+ # See http://origen-sdk.org/origen/api/Origen/Application/Configuration.html
5
+ # for a full list of the configuration options available
6
+
7
+ # These attributes should never be changed, the duplication here will be resolved in future
8
+ # by condensing these attributes that do similar things
9
+ self.name = "test_ids"
10
+ self.namespace = "TestIds"
11
+ config.name = "test_ids"
12
+ config.initials = "TestIds"
13
+ # Change this to point to the revision control repository for this plugin
14
+ config.rc_url = "ssh://git@github.com:Origen-SDK/test_ids.git"
15
+ config.release_externally = true
16
+
17
+ # To enable deployment of your documentation to a web server (via the 'origen web'
18
+ # command) fill in these attributes.
19
+ config.web_directory = "git@github.com:Origen-SDK/Origen-SDK.github.io.git/test_ids"
20
+ config.web_domain = "http://origen-sdk.org/test_ids"
21
+
22
+ # When false Origen will be less strict about checking for some common coding errors,
23
+ # it is recommended that you leave this to true for better feedback and easier debug.
24
+ # This will be the default setting in Origen v3.
25
+ config.strict_errors = true
26
+
27
+ # See: http://origen-sdk.org/origen/latest/guides/utilities/lint/
28
+ config.lint_test = {
29
+ # Require the lint tests to pass before allowing a release to proceed
30
+ run_on_tag: true,
31
+ # Auto correct violations where possible whenever 'origen lint' is run
32
+ auto_correct: true,
33
+ # Limit the testing for large legacy applications
34
+ #level: :easy,
35
+ # Run on these directories/files by default
36
+ #files: ["lib", "config/application.rb"],
37
+ }
38
+
39
+ config.semantically_version = true
40
+
41
+ # An example of how to set application specific LSF parameters
42
+ #config.lsf.project = "msg.te"
43
+
44
+ # An example of how to specify a prefix to add to all generated patterns
45
+ #config.pattern_prefix = "nvm"
46
+
47
+ # An example of how to add header comments to all generated patterns
48
+ #config.pattern_header do
49
+ # cc "This is a pattern created by the example origen application"
50
+ #end
51
+
52
+ # By default all generated output will end up in ./output.
53
+ # Here you can specify an alternative directory entirely, or make it dynamic such that
54
+ # the output ends up in a setup specific directory.
55
+ #config.output_directory do
56
+ # "#{Origen.root}/output/#{$dut.class}"
57
+ #end
58
+
59
+ # Similarly for the reference files, generally you want to setup the reference directory
60
+ # structure to mirror that of your output directory structure.
61
+ #config.reference_directory do
62
+ # "#{Origen.root}/.ref/#{$dut.class}"
63
+ #end
64
+
65
+ # This will automatically deploy your documentation after every tag
66
+ def after_release_email(tag, note, type, selector, options)
67
+ command = "origen web compile --remote --api"
68
+ Dir.chdir Origen.root do
69
+ system command
70
+ end
71
+ end
72
+
73
+ # Ensure that all tests pass before allowing a release to continue
74
+ def validate_release
75
+ if !system("origen specs") || !system("origen examples")
76
+ puts "Sorry but you can't release with failing tests, please fix them and try again."
77
+ exit 1
78
+ else
79
+ puts "All tests passing, proceeding with release process!"
80
+ end
81
+ end
82
+
83
+ # To enabled source-less pattern generation create a class (for example PatternDispatcher)
84
+ # to generate the pattern. This should return false if the requested pattern has been
85
+ # dispatched, otherwise Origen will proceed with looking up a pattern source as normal.
86
+ #def before_pattern_lookup(requested_pattern)
87
+ # PatternDispatcher.new.dispatch_or_return(requested_pattern)
88
+ #end
89
+
90
+ # If you use pattern iterators you may come across the case where you request a pattern
91
+ # like this:
92
+ # origen g example_pat_b0.atp
93
+ #
94
+ # However it cannot be found by Origen since the pattern name is actually example_pat_bx.atp
95
+ # In the case where the pattern cannot be found Origen will pass the name to this translator
96
+ # if it exists, and here you can make any substitutions to help Origen find the file you
97
+ # want. In this example any instances of _b\d, where \d means a number, are replaced by
98
+ # _bx.
99
+ #config.pattern_name_translator do |name|
100
+ # name.gsub(/_b\d/, "_bx")
101
+ #end
102
+
103
+ end
data/config/boot.rb ADDED
@@ -0,0 +1,24 @@
1
+ # This file is used to boot your plugin when it is running in standalone mode
2
+ # from its own workspace - i.e. when the plugin is being developed.
3
+ #
4
+ # It will not be loaded when the plugin is imported by a 3rd party app - in that
5
+ # case only lib/test_ids.rb is loaded.
6
+ #
7
+ # Therefore this file can be used to load anything extra that you need to boot
8
+ # the development environment for this app. For example, this is typically used
9
+ # to load some additional test classes to use your plugin APIs so that they can
10
+ # be tested and/or interacted with in the console.
11
+ require "test_ids"
12
+
13
+ module TestIdsDev
14
+ # Example of how to explicitly require a file
15
+ # require "test_ids_dev/my_file"
16
+
17
+ # Load all files in the lib/test_ids_dev directory.
18
+ # Note that there is no problem from requiring a file twice (Ruby will ignore
19
+ # the second require), so if you have a file that must be required first, then
20
+ # explicitly require it up above and then let this take care of the rest.
21
+ Dir.glob("#{File.dirname(__FILE__)}/../lib/test_ids_dev/**/*.rb").sort.each do |file|
22
+ require file
23
+ end
24
+ end
@@ -0,0 +1,71 @@
1
+ # This file should be used to extend the origen with application specific commands
2
+
3
+ # Map any command aliases here, for example to allow 'origen ex' to refer to a
4
+ # command called execute you would add a reference as shown below:
5
+ aliases ={
6
+ # "ex" => "execute",
7
+ }
8
+
9
+ # The requested command is passed in here as @command, this checks it against
10
+ # the above alias table and should not be removed.
11
+ @command = aliases[@command] || @command
12
+
13
+ # Now branch to the specific task code
14
+ case @command
15
+
16
+ # Here is an example of how to implement a command, the logic can go straight
17
+ # in here or you can require an external file if preferred.
18
+ when "my_command"
19
+ puts "Doing something..."
20
+ #require "commands/my_command" # Would load file lib/commands/my_command.rb
21
+ # You must always exit upon successfully capturing a command to prevent
22
+ # control flowing back to Origen
23
+ exit 0
24
+
25
+ # Example of how to make a command to run unit tests, this simply invokes RSpec on
26
+ # the spec directory
27
+ when "specs"
28
+ require "rspec"
29
+ exit RSpec::Core::Runner.run(['spec'])
30
+
31
+ # Example of how to make a command to run diff-based tests
32
+ when "examples", "test"
33
+ Origen.load_application
34
+ status = 0
35
+
36
+ # Program generator integration test
37
+ ARGV = %w(program/prb1.rb -t default -e default -r approved)
38
+ load "#{Origen.top}/lib/origen/commands/program.rb"
39
+
40
+ if Origen.app.stats.changed_files == 0 &&
41
+ Origen.app.stats.new_files == 0 &&
42
+ Origen.app.stats.changed_patterns == 0 &&
43
+ Origen.app.stats.new_patterns == 0
44
+
45
+ Origen.app.stats.report_pass
46
+ else
47
+ Origen.app.stats.report_fail
48
+ status = 1
49
+ end
50
+ puts
51
+ if @command == "test"
52
+ Origen.app.unload_target!
53
+ require "rspec"
54
+ result = RSpec::Core::Runner.run(['spec'])
55
+ status = status == 1 ? 1 : result
56
+ end
57
+ exit status # Exit with a 1 on the event of a failure per std unix result codes
58
+
59
+ # Always leave an else clause to allow control to fall back through to the
60
+ # Origen command handler.
61
+ else
62
+ # You probably want to also add the your commands to the help shown via
63
+ # origen -h, you can do this be assigning the required text to @application_commands
64
+ # before handing control back to Origen. Un-comment the example below to get started.
65
+ @application_commands = <<-EOT
66
+ specs Run the specs (tests), -c will enable coverage
67
+ examples Run the examples (tests), -c will enable coverage
68
+ test Run both specs and examples, -c will enable coverage
69
+ EOT
70
+
71
+ end
data/config/version.rb ADDED
@@ -0,0 +1,8 @@
1
+ module TestIds
2
+ MAJOR = 0
3
+ MINOR = 2
4
+ BUGFIX = 0
5
+ DEV = nil
6
+
7
+ VERSION = [MAJOR, MINOR, BUGFIX].join(".") + (DEV ? ".pre#{DEV}" : '')
8
+ end
@@ -0,0 +1,6 @@
1
+ # You can define any Rake tasks to support your application here (or in any file
2
+ # ending in .rake in this directory).
3
+ #
4
+ # Rake (Ruby Make) is very useful for creating build scripts, see this short video
5
+ # for a quick introduction:
6
+ # http://railscasts.com/episodes/66-custom-rake-tasks
@@ -0,0 +1,359 @@
1
+ module TestIds
2
+ class Allocator
3
+ include Origen::Callbacks
4
+ attr_reader :config
5
+
6
+ def initialize
7
+ @@allocators ||= 0
8
+ @@allocators += 1
9
+ if @@allocators > 1 && !TestIds.send(:testing?)
10
+ fail 'TestIds::Allocators is a singleton, there can be only one'
11
+ end
12
+ end
13
+
14
+ # Main method to inject generated bin and test numbers, the given
15
+ # options instance is modified accordingly
16
+ def allocate(instance, options)
17
+ @changes_made = true
18
+ clean(options)
19
+ @callbacks = []
20
+ name = extract_test_name(instance, options)
21
+ name = "#{name}_#{options[:index]}" if options[:index]
22
+ store['tests'][name] ||= {}
23
+ t = store['tests'][name]
24
+ # If the user has supplied any of these, that number should be used
25
+ # and reserved so that it is not automatically generated later
26
+ if options[:bin]
27
+ t['bin'] = options[:bin]
28
+ store['manually_assigned']['bin'][options[:bin].to_s] = true
29
+ # Regenerate the bin if the original allocation has since been applied
30
+ # manually elsewhere
31
+ elsif store['manually_assigned']['bin'][t['bin'].to_s]
32
+ t['bin'] = nil
33
+ # Also regenerate these as they could be a function of the bin
34
+ t['softbin'] = nil if config.softbins.function?
35
+ t['number'] = nil if config.numbers.function?
36
+ end
37
+ if options[:softbin]
38
+ t['softbin'] = options[:softbin]
39
+ store['manually_assigned']['softbin'][options[:softbin].to_s] = true
40
+ elsif store['manually_assigned']['softbin'][t['softbin'].to_s]
41
+ t['softbin'] = nil
42
+ # Also regenerate the number as it could be a function of the softbin
43
+ t['number'] = nil if config.numbers.function?
44
+ end
45
+ if options[:number]
46
+ t['number'] = options[:number]
47
+ store['manually_assigned']['number'][options[:number].to_s] = true
48
+ elsif store['manually_assigned']['number'][t['number'].to_s]
49
+ t['number'] = nil
50
+ end
51
+ # Otherwise generate the missing ones
52
+ t['bin'] ||= allocate_bin
53
+ t['softbin'] ||= allocate_softbin(t['bin'])
54
+ t['number'] ||= allocate_number(t['bin'], t['softbin'])
55
+ # Record that there has been a reference to the final numbers
56
+ time = Time.now.to_f
57
+ store['references']['bin'][t['bin'].to_s] = time if t['bin']
58
+ store['references']['softbin'][t['softbin'].to_s] = time if t['softbin']
59
+ store['references']['number'][t['number'].to_s] = time if t['number']
60
+ # Update the supplied options hash that will be forwarded to the
61
+ # program generator
62
+ options[:bin] = t['bin']
63
+ options[:softbin] = t['softbin']
64
+ options[:number] = t['number']
65
+ options
66
+ end
67
+
68
+ def config
69
+ TestIds.config
70
+ end
71
+
72
+ def store
73
+ @store ||= begin
74
+ s = JSON.load(File.read(file)) if file && File.exist?(file)
75
+ if s
76
+ @last_bin = s['pointers']['bin']
77
+ @last_softbin = s['pointers']['softbin']
78
+ @last_number = s['pointers']['number']
79
+ s
80
+ else
81
+ { 'tests' => {},
82
+ 'manually_assigned' => { 'bin' => {}, 'softbin' => {}, 'number' => {} },
83
+ 'pointers' => { 'bin' => nil, 'softbin' => nil, 'number' => nil },
84
+ 'references' => { 'bin' => {}, 'softbin' => {}, 'number' => {} }
85
+ }
86
+ end
87
+ end
88
+ end
89
+
90
+ # Saves the current allocator state to the repository
91
+ def save
92
+ if file
93
+ p = Pathname.new(file)
94
+ FileUtils.mkdir_p(p.dirname)
95
+ File.open(p, 'w') { |f| f.puts JSON.pretty_generate(store) }
96
+ end
97
+ end
98
+
99
+ def on_origen_shutdown
100
+ unless TestIds.send(:testing?)
101
+ if config.repo && @changes_made && config.on_completion != :discard
102
+ save
103
+ git.publish if publish?
104
+ end
105
+ end
106
+ end
107
+
108
+ # Returns a path to the file that will be used to store the allocated bins/numbers.
109
+ # If config.repo has not been set it returns nil.
110
+ def file
111
+ if config.repo
112
+ @file ||= begin
113
+ if git?
114
+ dir = "#{Origen.app.imports_directory}/test_ids/#{Pathname.new(config.repo).basename}"
115
+ FileUtils.mkdir_p(dir)
116
+ "#{dir}/store.json"
117
+ else
118
+ config.repo
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ def git
125
+ @git ||= Git.new(local: Pathname.new(file).dirname, remote: config.repo, no_pull: publish?)
126
+ end
127
+
128
+ def prepare
129
+ if git?
130
+ git # Pulls the latest repo
131
+ git.get_lock if publish?
132
+ end
133
+ end
134
+
135
+ private
136
+
137
+ def publish?
138
+ git? && config.on_completion == :publish
139
+ end
140
+
141
+ def git?
142
+ if config.repo
143
+ !!(config.repo =~ /git/i)
144
+ end
145
+ end
146
+
147
+ # Returns the next available bin in the pool, if they have all been given out
148
+ # the one that hasn't been used for the longest time will be given out
149
+ def allocate_bin
150
+ return nil if config.bins.empty?
151
+ if store['pointers']['bin'] == 'done'
152
+ reclaim_bin
153
+ else
154
+ b = config.bins.include.next(@last_bin)
155
+ @last_bin = nil
156
+ while b && (store['manually_assigned']['bin'][b.to_s] || config.bins.exclude.include?(b))
157
+ b = config.bins.include.next
158
+ end
159
+ # When no bin is returned it means we have used them all, all future generation
160
+ # now switches to reclaim mode
161
+ if b
162
+ store['pointers']['bin'] = b
163
+ else
164
+ store['pointers']['bin'] = 'done'
165
+ reclaim_bin
166
+ end
167
+ end
168
+ end
169
+
170
+ def reclaim_bin
171
+ store['references']['bin'] = store['references']['bin'].sort_by { |k, v| v }.to_h
172
+ store['references']['bin'].first[0].to_i
173
+ end
174
+
175
+ def allocate_softbin(bin)
176
+ return nil if config.softbins.empty?
177
+ if config.softbins.algorithm
178
+ algo = config.softbins.algorithm.to_s.downcase
179
+ if algo.to_s =~ /^[b\dx]+$/
180
+ number = algo.to_s
181
+ bin = bin.to_s
182
+ if number =~ /(b+)/
183
+ max_bin_size = Regexp.last_match(1).size
184
+ if bin.size > max_bin_size
185
+ fail "Bin number (#{bin}) overflows the test number algorithm (#{algo})"
186
+ end
187
+ number = number.sub(/b+/, bin.rjust(max_bin_size, '0'))
188
+ end
189
+ if number =~ /(x+)/
190
+ max_counter_size = Regexp.last_match(1).size
191
+ refs = store['references']['softbin']
192
+ i = 0
193
+ possible = []
194
+ proposal = number.sub(/x+/, i.to_s.rjust(max_counter_size, '0'))
195
+ possible << proposal
196
+ while refs[proposal] && i.to_s.size <= max_counter_size
197
+ i += 1
198
+ proposal = number.sub(/x+/, i.to_s.rjust(max_counter_size, '0'))
199
+ possible << proposal
200
+ end
201
+ # Overflowed, need to go search for the oldest duplicate now
202
+ if i.to_s.size > max_counter_size
203
+ i = 0
204
+ # Not the most efficient search algorithm, but this should be hit very rarely
205
+ # and even then only to generate the bin the first time around
206
+ p = refs.sort_by { |bin, last_used| last_used }.find do |bin, last_used|
207
+ possible.include?(bin)
208
+ end
209
+ proposal = p[0]
210
+ end
211
+ number = proposal
212
+ end
213
+ else
214
+ fail "Unknown softbin algorithm: #{algo}"
215
+ end
216
+ number.to_i
217
+ elsif callback = config.softbins.callback
218
+ callback.call(bin)
219
+ else
220
+ if store['pointers']['softbin'] == 'done'
221
+ reclaim_softbin
222
+ else
223
+ b = config.softbins.include.next(@last_softbin)
224
+ @last_softbin = nil
225
+ while b && (store['manually_assigned']['softbin'][b.to_s] || config.softbins.exclude.include?(b))
226
+ b = config.softbins.include.next
227
+ end
228
+ # When no softbin is returned it means we have used them all, all future generation
229
+ # now switches to reclaim mode
230
+ if b
231
+ store['pointers']['softbin'] = b
232
+ else
233
+ store['pointers']['softbin'] = 'done'
234
+ reclaim_softbin
235
+ end
236
+ end
237
+ end
238
+ end
239
+
240
+ def reclaim_softbin
241
+ store['references']['softbin'] = store['references']['softbin'].sort_by { |k, v| v }.to_h
242
+ store['references']['softbin'].first[0].to_i
243
+ end
244
+
245
+ def allocate_number(bin, softbin)
246
+ return nil if config.numbers.empty?
247
+ if config.numbers.algorithm
248
+ algo = config.numbers.algorithm.to_s.downcase
249
+ if algo.to_s =~ /^[bs\dx]+$/
250
+ number = algo.to_s
251
+ bin = bin.to_s
252
+ if number =~ /(b+)/
253
+ max_bin_size = Regexp.last_match(1).size
254
+ if bin.size > max_bin_size
255
+ fail "Bin number (#{bin}) overflows the test number algorithm (#{algo})"
256
+ end
257
+ number = number.sub(/b+/, bin.rjust(max_bin_size, '0'))
258
+ end
259
+ softbin = softbin.to_s
260
+ if number =~ /(s+)/
261
+ max_softbin_size = Regexp.last_match(1).size
262
+ if softbin.size > max_softbin_size
263
+ fail "Softbin number (#{softbin}) overflows the test number algorithm (#{algo})"
264
+ end
265
+ number = number.sub(/s+/, softbin.rjust(max_bin_size, '0'))
266
+ end
267
+ if number =~ /(x+)/
268
+ max_counter_size = Regexp.last_match(1).size
269
+ refs = store['references']['number']
270
+ i = 0
271
+ possible = []
272
+ proposal = number.sub(/x+/, i.to_s.rjust(max_counter_size, '0'))
273
+ possible << proposal
274
+ while refs[proposal] && i.to_s.size <= max_counter_size
275
+ i += 1
276
+ proposal = number.sub(/x+/, i.to_s.rjust(max_counter_size, '0'))
277
+ possible << proposal
278
+ end
279
+ # Overflowed, need to go search for the oldest duplicate now
280
+ if i.to_s.size > max_counter_size
281
+ i = 0
282
+ # Not the most efficient search algorithm, but this should be hit very rarely
283
+ # and even then only to generate the bin the first time around
284
+ p = refs.sort_by { |bin, last_used| last_used }.find do |bin, last_used|
285
+ possible.include?(bin)
286
+ end
287
+ proposal = p[0]
288
+ end
289
+ number = proposal
290
+ end
291
+ number.to_i
292
+ else
293
+ fail "Unknown test number algorithm: #{algo}"
294
+ end
295
+ elsif callback = config.numbers.callback
296
+ callback.call(bin, softbin)
297
+ else
298
+ if store['pointers']['number'] == 'done'
299
+ reclaim_number
300
+ else
301
+ b = config.numbers.include.next(@last_number)
302
+ @last_number = nil
303
+ while b && (store['manually_assigned']['number'][b.to_s] || config.numbers.exclude.include?(b))
304
+ b = config.numbers.include.next
305
+ end
306
+ # When no number is returned it means we have used them all, all future generation
307
+ # now switches to reclaim mode
308
+ if b
309
+ store['pointers']['number'] = b
310
+ else
311
+ store['pointers']['number'] = 'done'
312
+ reclaim_number
313
+ end
314
+ end
315
+ end
316
+ end
317
+
318
+ def reclaim_number
319
+ store['references']['number'] = store['references']['number'].sort_by { |k, v| v }.to_h
320
+ store['references']['number'].first[0].to_i
321
+ end
322
+
323
+ def extract_test_name(instance, options)
324
+ name = options[:name]
325
+ unless name
326
+ if instance.is_a?(String)
327
+ name = instance
328
+ elsif instance.is_a?(Symbol)
329
+ name = instance.to_s
330
+ elsif instance.is_a?(Hash)
331
+ h = instance.with_indifferent_access
332
+ name = h[:name] || h[:tname] || h[:testname] || h[:test_name]
333
+ elsif instance.respond_to?(:name)
334
+ name = instance.name
335
+ else
336
+ fail "Could not get the test name from #{instance}"
337
+ end
338
+ end
339
+ name.to_s.downcase
340
+ end
341
+
342
+ # Cleans the given options hash by consolidating any bin/test numbers
343
+ # to the following option keys:
344
+ #
345
+ # * :bin
346
+ # * :softbin
347
+ # * :number
348
+ # * :name
349
+ # * :index
350
+ def clean(options)
351
+ options[:softbin] ||= options.delete(:sbin) || options.delete(:soft_bin)
352
+ options[:number] ||= options.delete(:test_number) || options.delete(:tnum) ||
353
+ options.delete(:testnumber)
354
+ options[:name] ||= options.delete(:tname) || options.delete(:testname) ||
355
+ options.delete(:test_name)
356
+ options[:index] ||= options.delete(:ix)
357
+ end
358
+ end
359
+ end
@@ -0,0 +1,117 @@
1
+ module TestIds
2
+ class BinArray
3
+ def initialize
4
+ @store = []
5
+ end
6
+
7
+ def <<(val)
8
+ @store += Array(val)
9
+ @store = @store.sort do |a, b|
10
+ a = a.min if a.is_a?(Range)
11
+ b = b.min if b.is_a?(Range)
12
+ a <=> b
13
+ end
14
+ nil
15
+ end
16
+
17
+ def empty?
18
+ @store.empty?
19
+ end
20
+
21
+ def freeze
22
+ @store.freeze
23
+ end
24
+
25
+ # Returns true if the array contains the given bin number
26
+ def include?(bin)
27
+ @store.any? do |v|
28
+ v == bin || (v.is_a?(Range) && bin >= v.min && bin <= v.max)
29
+ end
30
+ end
31
+
32
+ # Returns the next bin in the array, starting from the first and remembering the last bin
33
+ # when called the next time.
34
+ # A bin can optionally be supplied in which case the internal pointer will be reset and the
35
+ # next bin that occurs after the given number will be returned.
36
+ def next(after = nil)
37
+ if after
38
+ # Need to work out the pointer here as it is probably out of sync with the
39
+ # last value now
40
+ @pointer = nil
41
+ i = 0
42
+ until @pointer
43
+ v = @store[i]
44
+ if v
45
+ if after == v || (v.is_a?(Range) && after >= v.min && after <= v.max)
46
+ @pointer = i
47
+ @next = after
48
+ elsif after < min_val(v)
49
+ @pointer = previous_pointer(i)
50
+ @next = min_val(v) - 1
51
+ end
52
+ else
53
+ # Gone past the end of the array
54
+ @pointer = @store.size - 1
55
+ @next = min_val(@store[0]) - 1
56
+ end
57
+ i += 1
58
+ end
59
+ end
60
+ if @next
61
+ @pointer ||= 0
62
+ if @store[@pointer].is_a?(Range) && @next != @store[@pointer].max
63
+ @next += 1
64
+ else
65
+ @pointer += 1
66
+ # Return nil when we get to the end of the array
67
+ if @pointer == @store.size
68
+ @pointer -= 1
69
+ return nil
70
+ end
71
+ @next = @store[@pointer]
72
+ @next = @next.min if @next.is_a?(Range)
73
+ end
74
+ else
75
+ v = @store.first
76
+ if v.is_a?(Range)
77
+ @next = v.min
78
+ else
79
+ @next = v
80
+ end
81
+ end
82
+ @next
83
+ end
84
+
85
+ def min
86
+ v = @store.first
87
+ if v.is_a?(Range)
88
+ v.min
89
+ else
90
+ v
91
+ end
92
+ end
93
+
94
+ def max
95
+ v = @store.last
96
+ if v.is_a?(Range)
97
+ v.max
98
+ else
99
+ v
100
+ end
101
+ end
102
+
103
+ private
104
+
105
+ def previous_pointer(i)
106
+ i == 0 ? @store.size - 1 : i - 1
107
+ end
108
+
109
+ def min_val(v)
110
+ v.is_a?(Range) ? v.min : v
111
+ end
112
+
113
+ def max_val(v)
114
+ v.is_a?(Range) ? v.max : v
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,106 @@
1
+ module TestIds
2
+ class Configuration
3
+ class Item
4
+ attr_accessor :include, :exclude, :algorithm
5
+
6
+ def initialize
7
+ @include = BinArray.new
8
+ @exclude = BinArray.new
9
+ end
10
+
11
+ def callback(&block)
12
+ if block_given?
13
+ @callback = block
14
+ else
15
+ @callback
16
+ end
17
+ end
18
+
19
+ def empty?
20
+ include.empty? && exclude.empty? && !algorithm && !callback
21
+ end
22
+
23
+ def function?
24
+ !!algorithm || !!callback
25
+ end
26
+
27
+ def freeze
28
+ @include.freeze
29
+ @exclude.freeze
30
+ super
31
+ end
32
+ end
33
+
34
+ attr_accessor :repo
35
+ attr_reader :on_completion
36
+
37
+ def on_completion
38
+ @on_completion || :publish
39
+ end
40
+
41
+ def on_completion=(val)
42
+ unless %w(publish save discard).include?(val.to_s)
43
+ fail 'on_completion must be set to one of: :publish, :save, :discard'
44
+ end
45
+ @on_completion = val.to_sym
46
+ end
47
+
48
+ def bins
49
+ @bins ||= Item.new
50
+ end
51
+
52
+ def softbins
53
+ @softbins ||= Item.new
54
+ if block_given?
55
+ @softbins.callback(&block)
56
+ end
57
+ @softbins
58
+ end
59
+
60
+ # An alias for config.softbins.algorithm=
61
+ def softbins=(val)
62
+ softbins.algorithm = val
63
+ end
64
+
65
+ def numbers(&block)
66
+ @numbers ||= Item.new
67
+ if block_given?
68
+ @numbers.callback(&block)
69
+ end
70
+ @numbers
71
+ end
72
+
73
+ # An alias for config.numbers.algorithm=
74
+ def numbers=(val)
75
+ numbers.algorithm = val
76
+ end
77
+
78
+ def validate!
79
+ unless validated?
80
+ if bins.algorithm
81
+ fail 'The TestIds bins configuration cannot be set to an algorithm, only a range set by bins.include and bins.exclude is permitted'
82
+ end
83
+ if bins.callback
84
+ fail 'The TestIds bins configuration cannot be set by a callback, only a range set by bins.include and bins.exclude is permitted'
85
+ end
86
+ @validated = true
87
+ freeze
88
+ end
89
+ end
90
+
91
+ def empty?
92
+ bins.empty? && softbins.empty? && numbers.empty?
93
+ end
94
+
95
+ def validated?
96
+ @validated
97
+ end
98
+
99
+ def freeze
100
+ bins.freeze
101
+ softbins.freeze
102
+ numbers.freeze
103
+ super
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,116 @@
1
+ require 'json'
2
+ require 'git'
3
+ module TestIds
4
+ # The Git driver is responsible for committing and fetching the
5
+ # store from the central Git repository.
6
+ #
7
+ # All operations are automatically pushed immediately to the central repository
8
+ # and a lock will be taken out whenever a program generation operation is done in
9
+ # production mode to prevent the need to merge with other users.
10
+ #
11
+ # An instance of this class is instantiated as TestIds.git
12
+ class Git
13
+ attr_reader :repo, :local
14
+
15
+ def initialize(options)
16
+ unless File.exist?("#{options[:local]}/.git")
17
+ FileUtils.rm_rf(options[:local]) if File.exist?(options[:local])
18
+ FileUtils.mkdir_p(options[:local])
19
+ Dir.chdir options[:local] do
20
+ `git clone #{options[:remote]} .`
21
+ if !File.exist?('store.json') || !File.exist?('lock.json')
22
+ # Should really try to use the Git driver for this
23
+ exec 'touch store.json lock.json'
24
+ exec 'git add store.json lock.json'
25
+ exec 'git commit -m "Initial commit"'
26
+ exec 'git push'
27
+ end
28
+ end
29
+ end
30
+ @local = options[:local]
31
+ @repo = ::Git.open(options[:local])
32
+ @repo.reset_hard
33
+ @repo.pull unless options[:no_pull]
34
+ end
35
+
36
+ def exec(cmd)
37
+ r = system(cmd)
38
+ unless r
39
+ fail "Something went wrong running command: #{cmd}"
40
+ end
41
+ end
42
+
43
+ def publish
44
+ write('store.json')
45
+ release_lock
46
+ repo.commit('Publishing latest store')
47
+ repo.push('origin')
48
+ end
49
+
50
+ # Writes the data to the given file and pushes to the remote repo
51
+ def write(path, data = nil)
52
+ f = File.join(local, path)
53
+ File.write(f, data) if data
54
+ repo.add(f)
55
+ end
56
+
57
+ def get_lock
58
+ until available_to_lock?
59
+ puts "Waiting for lock, currently locked by #{lock_user} (the lock will expire in less than #{lock_minutes_remaining} #{'minute'.pluralize(lock_minutes_remaining)} if not released before that)"
60
+ sleep 5
61
+ end
62
+ data = {
63
+ 'user' => User.current.name,
64
+ 'expires' => (Time.now + minutes(5)).to_f
65
+ }
66
+ write('lock.json', JSON.pretty_generate(data))
67
+ repo.commit('Obtaining lock')
68
+ repo.push('origin')
69
+ end
70
+
71
+ def release_lock
72
+ data = {
73
+ 'user' => nil,
74
+ 'expires' => nil
75
+ }
76
+ write('lock.json', JSON.pretty_generate(data))
77
+ end
78
+
79
+ def with_lock
80
+ get_lock
81
+ yield
82
+ ensure
83
+ release_lock
84
+ end
85
+
86
+ def available_to_lock?
87
+ repo.pull
88
+ if lock_content && lock_user && lock_user != User.current.name
89
+ Time.now.to_f > lock_expires
90
+ else
91
+ true
92
+ end
93
+ end
94
+
95
+ def lock_minutes_remaining
96
+ ((lock_expires - Time.now.to_f) / 60).ceil
97
+ end
98
+
99
+ def lock_expires
100
+ lock_content['expires']
101
+ end
102
+
103
+ def lock_user
104
+ lock_content['user']
105
+ end
106
+
107
+ def lock_content
108
+ f = File.join(local, 'lock.json')
109
+ JSON.load(File.read(f)) if File.exist?(f)
110
+ end
111
+
112
+ def minutes(number)
113
+ number * 60
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,14 @@
1
+ require 'origen_testers/flow'
2
+ module OrigenTesters
3
+ module Flow
4
+ # Override the flow.test method to inject our generated bin and
5
+ # test numbers
6
+ alias_method :_orig_test, :test
7
+ def test(instance, options = {})
8
+ unless TestIds.config.empty?
9
+ TestIds.allocator.allocate(instance, options)
10
+ end
11
+ _orig_test(instance, options)
12
+ end
13
+ end
14
+ end
data/lib/test_ids.rb ADDED
@@ -0,0 +1,76 @@
1
+ require 'origen'
2
+ require_relative '../config/application.rb'
3
+ require 'origen_testers'
4
+
5
+ module TestIds
6
+ # THIS FILE SHOULD ONLY BE USED TO LOAD RUNTIME DEPENDENCIES
7
+ # If this plugin has any development dependencies (e.g. dummy DUT or other models that are only used
8
+ # for testing), then these should be loaded from config/boot.rb
9
+
10
+ # Example of how to explicitly require a file
11
+ # require "test_ids/my_file"
12
+
13
+ # Load all files in the lib/test_ids directory.
14
+ # Note that there is no problem from requiring a file twice (Ruby will ignore
15
+ # the second require), so if you have a file that must be required first, then
16
+ # explicitly require it up above and then let this take care of the rest.
17
+ Dir.glob("#{File.dirname(__FILE__)}/test_ids/**/*.rb").sort.each do |file|
18
+ require file
19
+ end
20
+
21
+ class <<self
22
+ def store
23
+ unless @configuration
24
+ fail 'The test ID generator has to be configured before you can start using it'
25
+ end
26
+ @store ||= Store.new
27
+ end
28
+
29
+ def allocator
30
+ unless @configuration
31
+ fail 'The test ID generator has to be configured before you can start using it'
32
+ end
33
+ @allocator ||= Allocator.new
34
+ end
35
+
36
+ def configuration
37
+ if block_given?
38
+ configure do |config|
39
+ yield config
40
+ end
41
+ else
42
+ @configuration ||
43
+ fail('You have to create the configuration first before you can access it')
44
+ end
45
+ end
46
+ alias_method :config, :configuration
47
+
48
+ def configure
49
+ if @configuration
50
+ fail "You can't modify an existing test IDs configuration"
51
+ end
52
+ @configuration = Configuration.new
53
+ yield @configuration
54
+ @configuration.validate!
55
+ allocator.prepare
56
+ end
57
+
58
+ private
59
+
60
+ # For testing, clears all instances including the configuration
61
+ def reset
62
+ @git = nil
63
+ @store = nil
64
+ @allocator = nil
65
+ @configuration = nil
66
+ end
67
+
68
+ def testing=(val)
69
+ @testing = val
70
+ end
71
+
72
+ def testing?
73
+ !!@testing
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,7 @@
1
+ module TestIdsDev
2
+ class DUT
3
+ include Origen::TopLevel
4
+
5
+ attr_accessor :test_ids
6
+ end
7
+ end
@@ -0,0 +1,26 @@
1
+ module TestIdsDev
2
+ class Interface
3
+ include OrigenTesters::ProgramGenerators
4
+
5
+ def initialize(options = {})
6
+ case dut.test_ids
7
+ when 1
8
+ TestIds.configure do |config|
9
+ # Example of testing remote repo
10
+ # config.repo = 'ssh://git@sw-stash.freescale.net/~r49409/test_ids_repo.git'
11
+ config.bins.include << 3
12
+ config.bins.include << (10..20)
13
+ config.bins.exclude << 15
14
+ config.softbins = :bbbxx
15
+ config.numbers do |bin, softbin|
16
+ softbin * 100
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ def func(name, options = {})
23
+ flow.test(name, options)
24
+ end
25
+ end
26
+ end
data/program/prb1.rb ADDED
@@ -0,0 +1,6 @@
1
+ Flow.create do
2
+
3
+ func :t1
4
+ func :t2
5
+ func :t3
6
+ end
@@ -0,0 +1,11 @@
1
+ % render "layouts/basic.html" do
2
+
3
+ %# HTML tags can be embedded in mark down files if you want to do specific custom
4
+ %# formatting like this, but in most cases that is not required.
5
+ <h1><%= Origen.app.namespace %> <span style="font-size: 14px">(<%= Origen.app.version %>)</span></h1>
6
+
7
+ % lines = File.readlines("#{Origen.root}/README.md")
8
+ % lines.shift # Lose the heading, we have our own here
9
+ <%= lines.join('') %>
10
+
11
+ % end
@@ -0,0 +1,15 @@
1
+ ---
2
+ title: <%= options[:title] || Origen.config.name %>
3
+ ---
4
+ <%= render "partials/navbar.html", tab: options[:tab] %>
5
+
6
+ <div class="row">
7
+ %# The markdown attribute is important if you are going to include content written
8
+ %# in markdown, without this is will be included verbatim
9
+ <div class="span12" markdown="1">
10
+ <%= yield %>
11
+
12
+ <%= disqus_comments %>
13
+
14
+ </div>
15
+ </div>
@@ -0,0 +1,21 @@
1
+ <nav class="navbar navbar-inverse navbar-fixed-top">
2
+ <div class="container">
3
+ <div class="navbar-header">
4
+ <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
5
+ <span class="sr-only">Toggle navigation</span>
6
+ <span class="icon-bar"></span>
7
+ <span class="icon-bar"></span>
8
+ <span class="icon-bar"></span>
9
+ </button>
10
+ <a class="navbar-brand" href="<%= path "/" %>">Home</a>
11
+ </div>
12
+ <div id="navbar" class="collapse navbar-collapse">
13
+ <ul class="nav navbar-nav">
14
+ <li class="<%= options[:tab] == :api ? 'active' : '' %>"><a href="<%= path "/api/" %>">API</a></li>
15
+ <li><a href="https://github.com/Origen-SDK/test_ids">Github</a></li>
16
+ <li class="<%= options[:tab] == :release ? 'active' : '' %>"><a href="<%= path "/release_notes" %>">Release Notes</a></li>
17
+ </ul>
18
+ <%= import "origen/web/logo.html" %>
19
+ </div><!--/.nav-collapse -->
20
+ </div>
21
+ </nav>
@@ -0,0 +1,5 @@
1
+ % render "layouts/basic.html", tab: :release do
2
+
3
+ <%= render "#{Origen.root}/doc/history" %>
4
+
5
+ % end
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: test_ids
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Stephen McGinty
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-09-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: origen
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.7.25
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 0.7.25
27
+ - !ruby/object:Gem::Dependency
28
+ name: origen_testers
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: git
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description:
56
+ email:
57
+ - stephen.mcginty@nxp.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - config/application.rb
63
+ - config/boot.rb
64
+ - config/commands.rb
65
+ - config/version.rb
66
+ - lib/tasks/test_ids.rake
67
+ - lib/test_ids.rb
68
+ - lib/test_ids/allocator.rb
69
+ - lib/test_ids/bin_array.rb
70
+ - lib/test_ids/configuration.rb
71
+ - lib/test_ids/git.rb
72
+ - lib/test_ids/origen_testers/flow.rb
73
+ - lib/test_ids_dev/dut.rb
74
+ - lib/test_ids_dev/interface.rb
75
+ - program/prb1.rb
76
+ - templates/web/index.md.erb
77
+ - templates/web/layouts/_basic.html.erb
78
+ - templates/web/partials/_navbar.html.erb
79
+ - templates/web/release_notes.md.erb
80
+ homepage:
81
+ licenses: []
82
+ metadata: {}
83
+ post_install_message:
84
+ rdoc_options: []
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '2'
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: 1.8.11
97
+ requirements: []
98
+ rubyforge_project:
99
+ rubygems_version: 2.2.2
100
+ signing_key:
101
+ specification_version: 4
102
+ summary: Origen plugin to assign and track test program bins and test numbers
103
+ test_files: []