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.
@@ -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?(Origen::Tester::Doc)
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?(Origen::Tester::Doc)
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?(Origen::Tester::Doc)
189
+ if Origen.tester.is_a?(OrigenTesters::Doc)
190
190
  if options[:return_model]
191
- Origen::Tester::Doc.model.add_flow(filename(include_extension: false), to_yaml)
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
- max_pin_name_length = ordered_pins.map(&:name).max { |a, b| a.length <=> b.length }.length
512
- pin_widths = ordered_pins.map { |p| p.size - 1 }
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
- max_pin_name_length.times do |i|
515
- cc((' ' * 93) + ordered_pins.map.with_index { |p, x| ((p.name[i] || ' ') + ' ' * pin_widths[x]).gsub('_', '-') }.join(' '))
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
- max_pin_name_length = ordered_pins.map(&:name).max { |a, b| a.length <=> b.length }.length
348
- pin_widths = ordered_pins.map { |p| p.size - 1 }
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
- max_pin_name_length.times do |i|
351
- cc((' ' * 50) + ordered_pins.map.with_index { |p, x| ((p.name[i] || ' ') + ' ' * pin_widths[x]).gsub('_', '-') }.join(' '))
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 = Origen::Tester::Doc.allocate
24
+ x = OrigenTesters::Doc.allocate
26
25
  if Origen.app.with_html_doc_tester?
27
26
  x.html_mode = true
28
27
  end