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.
@@ -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