origen_testers 0.4.1 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|