origen_testers 0.4.1 → 0.5.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 +4 -4
- data/config/version.rb +2 -2
- data/lib/origen_testers/command_based_tester.rb +45 -0
- data/lib/origen_testers/doc/generator/flow.rb +69 -0
- data/lib/origen_testers/doc/generator/flow_line.rb +201 -0
- data/lib/origen_testers/doc/generator/test.rb +66 -0
- data/lib/origen_testers/doc/generator/test_group.rb +64 -0
- data/lib/origen_testers/doc/generator/tests.rb +45 -0
- data/lib/origen_testers/doc/generator.rb +124 -0
- data/lib/origen_testers/doc/model.rb +160 -0
- data/lib/origen_testers/doc.rb +224 -0
- data/lib/origen_testers/generator.rb +4 -4
- data/lib/origen_testers/igxl_based_tester/base.rb +6 -4
- data/lib/origen_testers/igxl_based_tester/j750.rb +0 -2
- data/lib/origen_testers/smartest_based_tester/base.rb +6 -4
- data/lib/origen_testers/vector_based_tester.rb +4 -5
- data/lib/origen_testers/vector_generator.rb +98 -81
- data/lib/origen_testers.rb +11 -9
- metadata +11 -2
@@ -0,0 +1,160 @@
|
|
1
|
+
module OrigenTesters
|
2
|
+
class Doc
|
3
|
+
# Class representing a program model, provides an API to iterate
|
4
|
+
# on the flow based on context (e.g. which job).
|
5
|
+
class Model
|
6
|
+
attr_accessor :flows, :target
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@flows = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
# Iterates through each line in the given flow returning a hash for each
|
13
|
+
# line with the following structure:
|
14
|
+
#
|
15
|
+
# {
|
16
|
+
# :type => Symbol, # Type of flow line
|
17
|
+
# :description => [], # Array of strings
|
18
|
+
# :instance => [{}], # Array of attributes hashes (each one represents an individual test instance)
|
19
|
+
# :flow => {}, # Hash of attributes
|
20
|
+
# :context => {}, # Hash of attributes
|
21
|
+
# }
|
22
|
+
#
|
23
|
+
# In all cases if an item is missing then it will be replaced by an empty
|
24
|
+
# array or hash as appropriate so that the caller does not need to worry
|
25
|
+
# about this.
|
26
|
+
#
|
27
|
+
# Supply the name of the flow(s) to consider, if the flows argument is left
|
28
|
+
# out then all lines in all flows will be returned.
|
29
|
+
#
|
30
|
+
# A context option can be supplied to only return the tests for which the given
|
31
|
+
# context is true.
|
32
|
+
#
|
33
|
+
# puts "The following tests run at FR:"
|
34
|
+
# program_model.each_in_flow(:ft_flow, :context => { :job => "FR" }) do |line|
|
35
|
+
# puts " #{line[:flow][:name]}"
|
36
|
+
# end
|
37
|
+
def each_in_flow(flows = nil, options = {})
|
38
|
+
if flows.is_a?(Hash)
|
39
|
+
options = flows
|
40
|
+
flows = nil
|
41
|
+
end
|
42
|
+
unless flows
|
43
|
+
flows = self.flows.keys
|
44
|
+
end
|
45
|
+
[flows].flatten.each do |flow|
|
46
|
+
@flows[flow.to_sym].each do |test|
|
47
|
+
test = format_test(test)
|
48
|
+
if valid_in_context?(test, options[:context])
|
49
|
+
yield test
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
|
56
|
+
# Searches the given flows to find dependents of the given test id. The dependents
|
57
|
+
# are returned in arrays grouped by context:
|
58
|
+
#
|
59
|
+
# {
|
60
|
+
# :if_failed => [],
|
61
|
+
# :if_passed => [],
|
62
|
+
# :if_ran => [],
|
63
|
+
# :unless_ran => [],
|
64
|
+
# }
|
65
|
+
#
|
66
|
+
# Each test will have the same format as described in #each_in_flow.
|
67
|
+
#
|
68
|
+
# If no dependents are found an empty hash is returned.
|
69
|
+
def dependents_of(id, flows, options = {})
|
70
|
+
d = {}
|
71
|
+
each_in_flow(flows, options) do |test|
|
72
|
+
test[:context].each do |key, val|
|
73
|
+
if val == id
|
74
|
+
d[key] ||= []
|
75
|
+
d[key] << test
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
d
|
80
|
+
end
|
81
|
+
|
82
|
+
# Search for the given test id in the given flows.
|
83
|
+
# Returns nil if not found.
|
84
|
+
def find_by_id(id, flows, options = {})
|
85
|
+
each_in_flow(flows, options) do |test|
|
86
|
+
return test if test[:flow][:id] == id
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns true if the given tests id valid under the given context
|
91
|
+
# (currently only tests for job matching)
|
92
|
+
def valid_in_context?(test, context = nil)
|
93
|
+
if context && context[:job] && test[:context]
|
94
|
+
if test[:context][:if_jobs]
|
95
|
+
test[:context][:if_jobs].include?(context[:job])
|
96
|
+
elsif test[:context][:unless_jobs]
|
97
|
+
!test[:context][:unless_jobs].include?(context[:job])
|
98
|
+
else
|
99
|
+
true
|
100
|
+
end
|
101
|
+
else
|
102
|
+
true
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# @api private
|
107
|
+
def format_test(test, _options = {})
|
108
|
+
{
|
109
|
+
type: test[:type] ? test[:type].to_sym : :unknown,
|
110
|
+
description: test[:description] || [],
|
111
|
+
instance: build_instance(test),
|
112
|
+
flow: test[:flow] ? test[:flow][:attributes] || {} : {},
|
113
|
+
context: test[:flow] ? test[:flow][:context] || {} : {}
|
114
|
+
}
|
115
|
+
end
|
116
|
+
|
117
|
+
# @api private
|
118
|
+
def build_instance(test)
|
119
|
+
if test[:instance]
|
120
|
+
if test[:instance][:group]
|
121
|
+
test[:instance][:group].map { |g| g[:attributes] || {} }
|
122
|
+
else
|
123
|
+
[test[:instance][:attributes] || {}]
|
124
|
+
end
|
125
|
+
else
|
126
|
+
[{}]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# YAML likes strings for keys, we don't, so make sure all keys are symbols
|
131
|
+
# when receiving a new flow
|
132
|
+
# @api private
|
133
|
+
def add_flow(name, content)
|
134
|
+
@flows[name.to_sym] = content.map do |h|
|
135
|
+
h = symbolize_keys h
|
136
|
+
if h[:instance] && h[:instance][:group]
|
137
|
+
h[:instance][:group] = h[:instance][:group].map { |j| symbolize_keys j }
|
138
|
+
end
|
139
|
+
h
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# @api private
|
144
|
+
def symbolize_keys(hash)
|
145
|
+
hash.reduce({}) do |result, (key, value)|
|
146
|
+
new_key = case key
|
147
|
+
when String then key.to_sym
|
148
|
+
else key
|
149
|
+
end
|
150
|
+
new_value = case value
|
151
|
+
when Hash then symbolize_keys(value)
|
152
|
+
else value
|
153
|
+
end
|
154
|
+
result[new_key] = new_value
|
155
|
+
result
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,224 @@
|
|
1
|
+
module OrigenTesters
|
2
|
+
# Tester model to generate test program documentation from your pattern sources.
|
3
|
+
# This is intended to be a drop in replacement for existing testers and instead
|
4
|
+
# of generating patterns or test program sheets it will generate a document object
|
5
|
+
# which you can then render out via a template or manually.
|
6
|
+
class Doc
|
7
|
+
include VectorBasedTester
|
8
|
+
|
9
|
+
autoload :Generator, 'origen_testers/doc/generator'
|
10
|
+
autoload :Model, 'origen_testers/doc/model'
|
11
|
+
|
12
|
+
attr_accessor :html_mode
|
13
|
+
|
14
|
+
def generate?
|
15
|
+
html_mode
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(*_args)
|
19
|
+
@pat_extension = 'md'
|
20
|
+
@indent = 0
|
21
|
+
@snip_counters = []
|
22
|
+
end
|
23
|
+
|
24
|
+
# Snip the number of comment lines generated by the contained block
|
25
|
+
# to the number given, this is useful for keeping files sizes down and
|
26
|
+
# is typically used to snip sections like downloading LRE code.
|
27
|
+
def snip(number, _options = {})
|
28
|
+
@snip_counters.push number + 1
|
29
|
+
yield
|
30
|
+
@snip_counters.pop
|
31
|
+
end
|
32
|
+
|
33
|
+
def pc(msg)
|
34
|
+
if @snip_counters.last
|
35
|
+
if @snip_counters.last == 1
|
36
|
+
@snip_counters[@snip_counters.size - 1] = 0
|
37
|
+
msg = ' ...snipped'
|
38
|
+
elsif @snip_counters.last == 0
|
39
|
+
return
|
40
|
+
else
|
41
|
+
@snip_counters[@snip_counters.size - 1] -= 1
|
42
|
+
end
|
43
|
+
end
|
44
|
+
if html_mode
|
45
|
+
push_comment(msg)
|
46
|
+
else
|
47
|
+
Origen.log.info((' ' * @indent) + msg)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def c1(msg, options = {})
|
52
|
+
if generating_program?
|
53
|
+
Origen.interface.doc_comments_capture(msg)
|
54
|
+
else
|
55
|
+
unless @inhibit_comments
|
56
|
+
options = {
|
57
|
+
prefix: true
|
58
|
+
}.merge(options)
|
59
|
+
if @step_comment_on
|
60
|
+
open_text_block
|
61
|
+
if options[:prefix]
|
62
|
+
pc "# #{msg}"
|
63
|
+
else
|
64
|
+
pc "#{msg}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def c2(msg, options = {})
|
72
|
+
unless @inhibit_comments
|
73
|
+
options = {
|
74
|
+
prefix: true
|
75
|
+
}.merge(options)
|
76
|
+
open_text_block
|
77
|
+
if options[:prefix]
|
78
|
+
pc "# #{msg}"
|
79
|
+
else
|
80
|
+
pc "#{msg}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def annotate(msg, _options)
|
86
|
+
unless @inhibit_comments
|
87
|
+
if html_mode
|
88
|
+
pc ''
|
89
|
+
lines = msg.split("\n")
|
90
|
+
leading_spaces = lines.first[/\A */].size
|
91
|
+
lines.each do |line|
|
92
|
+
pc line.gsub(/^.{#{leading_spaces}}/, '')
|
93
|
+
end
|
94
|
+
pc ''
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def open_text_block
|
100
|
+
if html_mode
|
101
|
+
unless @text_block_open
|
102
|
+
pc ''
|
103
|
+
pc '~~~text'
|
104
|
+
@text_block_open = true
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def close_text_block
|
110
|
+
if html_mode
|
111
|
+
if @text_block_open
|
112
|
+
pc '~~~'
|
113
|
+
pc ''
|
114
|
+
@text_block_open = false
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def pattern_name
|
120
|
+
Origen.app.current_job.output_pattern_filename.gsub('.md', '')
|
121
|
+
end
|
122
|
+
|
123
|
+
def pre_header
|
124
|
+
# pc "---"
|
125
|
+
# pc "layout: bootstrap"
|
126
|
+
# pc "title: #{pattern_name}"
|
127
|
+
# pc "gzip: false"
|
128
|
+
# pc "---"
|
129
|
+
# pc ""
|
130
|
+
# pc "# #{pattern_name}"
|
131
|
+
# pc ""
|
132
|
+
end
|
133
|
+
|
134
|
+
def pattern_section(msg)
|
135
|
+
unless @inhibit_comments
|
136
|
+
if generating_program?
|
137
|
+
Origen.interface.flow.start_section(name: msg)
|
138
|
+
yield
|
139
|
+
Origen.interface.flow.stop_section
|
140
|
+
else
|
141
|
+
if html_mode
|
142
|
+
counter = next_accordion_counter
|
143
|
+
close_text_block
|
144
|
+
pc ''
|
145
|
+
pc "<div class=\"accordion-group\">"
|
146
|
+
pc "<div class=\"accordion-heading\">"
|
147
|
+
pc "<a class=\"accordion-toggle\" data-toggle=\"collapse\" data-parent=\"#accordion2\" href=\"#collapseComment#{counter}\">"
|
148
|
+
pc "#{msg}"
|
149
|
+
pc '</a>'
|
150
|
+
pc '</div>'
|
151
|
+
pc "<div id=\"collapseComment#{counter}\" class=\"accordion-body collapse\">"
|
152
|
+
pc "<div class=\"accordion-inner\" markdown=\"1\">"
|
153
|
+
yield
|
154
|
+
close_text_block
|
155
|
+
pc '</div>'
|
156
|
+
pc '</div>'
|
157
|
+
pc '</div>'
|
158
|
+
else
|
159
|
+
pc ''
|
160
|
+
pc "#{msg}"
|
161
|
+
pc ''
|
162
|
+
@indent += 4
|
163
|
+
yield
|
164
|
+
@indent -= 4
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def next_accordion_counter
|
171
|
+
@accordion_counter ||= 0
|
172
|
+
@accordion_counter += 1
|
173
|
+
end
|
174
|
+
|
175
|
+
def ss(msg = nil)
|
176
|
+
unless @inhibit_comments
|
177
|
+
@step_comment_on = true
|
178
|
+
if block_given?
|
179
|
+
yield
|
180
|
+
else
|
181
|
+
c2(msg)
|
182
|
+
end
|
183
|
+
@step_comment_on = false
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def self.generate_program_model(files, options = {})
|
188
|
+
options = {
|
189
|
+
action: :program,
|
190
|
+
return_model: true,
|
191
|
+
skip_diff: true
|
192
|
+
}.merge(options)
|
193
|
+
Origen.app.with_doc_tester do
|
194
|
+
Origen.target.temporary = options[:target] if options[:target]
|
195
|
+
Origen.app.load_target!
|
196
|
+
Origen.interface.reset_globals
|
197
|
+
options[:files] = files
|
198
|
+
Origen.file_handler.preserve_and_clear_state do
|
199
|
+
Origen.app.runner.launch(options)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
model.target = Origen.target.name
|
203
|
+
model
|
204
|
+
end
|
205
|
+
|
206
|
+
def self.model
|
207
|
+
@model ||= Model.new
|
208
|
+
end
|
209
|
+
|
210
|
+
def doc?
|
211
|
+
true
|
212
|
+
end
|
213
|
+
|
214
|
+
def cycle(_options = {})
|
215
|
+
end
|
216
|
+
|
217
|
+
# Ignore any tester-specific methods
|
218
|
+
def method_missing(_method, *_args, &_block)
|
219
|
+
if block_given?
|
220
|
+
yield
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
@@ -154,7 +154,7 @@ module OrigenTesters
|
|
154
154
|
def write_to_file(options = {})
|
155
155
|
c = caller[0]
|
156
156
|
unless output_inhibited?
|
157
|
-
if defined? self.class::TEMPLATE || Origen.tester.is_a?(
|
157
|
+
if defined? self.class::TEMPLATE || Origen.tester.is_a?(OrigenTesters::Doc)
|
158
158
|
write_from_template(options)
|
159
159
|
else
|
160
160
|
fail "Don't know hot to write without a template!"
|
@@ -174,7 +174,7 @@ module OrigenTesters
|
|
174
174
|
# The use of a class variable to store the opened files means that it will be
|
175
175
|
# shared by all generators in this run.
|
176
176
|
@@opened_files ||= []
|
177
|
-
if @@opened_files.include?(output_file) && !Origen.tester.is_a?(
|
177
|
+
if @@opened_files.include?(output_file) && !Origen.tester.is_a?(OrigenTesters::Doc)
|
178
178
|
@append = true
|
179
179
|
Origen.file_handler.preserve_state do
|
180
180
|
File.open(output_file, 'a') do |out|
|
@@ -186,9 +186,9 @@ module OrigenTesters
|
|
186
186
|
else
|
187
187
|
@append = false
|
188
188
|
Origen.file_handler.preserve_state do
|
189
|
-
if Origen.tester.is_a?(
|
189
|
+
if Origen.tester.is_a?(OrigenTesters::Doc)
|
190
190
|
if options[:return_model]
|
191
|
-
|
191
|
+
OrigenTesters::Doc.model.add_flow(filename(include_extension: false), to_yaml)
|
192
192
|
else
|
193
193
|
Origen.file_handler.open_for_write(output_file) do |f|
|
194
194
|
f.puts YAML.dump(to_yaml(include_descriptions: false))
|
@@ -508,11 +508,13 @@ module OrigenTesters
|
|
508
508
|
|
509
509
|
# here indicate pattern header specific stuff
|
510
510
|
yield pin_list
|
511
|
-
|
512
|
-
|
511
|
+
if ordered_pins.size > 0
|
512
|
+
max_pin_name_length = ordered_pins.map(&:name).max { |a, b| a.length <=> b.length }.length
|
513
|
+
pin_widths = ordered_pins.map { |p| p.size - 1 }
|
513
514
|
|
514
|
-
|
515
|
-
|
515
|
+
max_pin_name_length.times do |i|
|
516
|
+
cc((' ' * 93) + ordered_pins.map.with_index { |p, x| ((p.name[i] || ' ') + ' ' * pin_widths[x]).gsub('_', '-') }.join(' '))
|
517
|
+
end
|
516
518
|
end
|
517
519
|
end
|
518
520
|
|
@@ -199,8 +199,6 @@ module OrigenTesters
|
|
199
199
|
match_conditions.each_with_index do |condition, i|
|
200
200
|
microcode "block_#{i}_matched_#{@unique_counter}:"
|
201
201
|
cycle(microcode: 'pop_loop icc')
|
202
|
-
end
|
203
|
-
if options[:clr_fail_post_match]
|
204
202
|
cycle(microcode: 'clr_fail')
|
205
203
|
end
|
206
204
|
if options[:on_block_match_goto]
|
@@ -344,11 +344,13 @@ module OrigenTesters
|
|
344
344
|
end
|
345
345
|
end.join(' ')
|
346
346
|
microcode "FORMAT #{pin_list};"
|
347
|
-
|
348
|
-
|
347
|
+
if ordered_pins.size > 0
|
348
|
+
max_pin_name_length = ordered_pins.map(&:name).max { |a, b| a.length <=> b.length }.length
|
349
|
+
pin_widths = ordered_pins.map { |p| p.size - 1 }
|
349
350
|
|
350
|
-
|
351
|
-
|
351
|
+
max_pin_name_length.times do |i|
|
352
|
+
cc((' ' * 50) + ordered_pins.map.with_index { |p, x| ((p.name[i] || ' ') + ' ' * pin_widths[x]).gsub('_', '-') }.join(' '))
|
353
|
+
end
|
352
354
|
end
|
353
355
|
end
|
354
356
|
|
@@ -9,11 +9,10 @@ module OrigenTesters
|
|
9
9
|
require 'origen_testers/timing'
|
10
10
|
require 'origen_testers/api'
|
11
11
|
|
12
|
-
include VectorGenerator
|
13
|
-
include Timing
|
14
|
-
include API
|
15
|
-
|
16
12
|
included do
|
13
|
+
include VectorGenerator
|
14
|
+
include Timing
|
15
|
+
include API
|
17
16
|
end
|
18
17
|
|
19
18
|
module ClassMethods # :nodoc:
|
@@ -22,7 +21,7 @@ module OrigenTesters
|
|
22
21
|
# a tester with Origen
|
23
22
|
def new(*args, &block) # :nodoc:
|
24
23
|
if Origen.app.with_doc_tester?
|
25
|
-
x =
|
24
|
+
x = OrigenTesters::Doc.allocate
|
26
25
|
if Origen.app.with_html_doc_tester?
|
27
26
|
x.html_mode = true
|
28
27
|
end
|