origen_testers 0.21.0 → 0.30.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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/config/version.rb +1 -2
  3. data/lib/origen_testers.rb +2 -1
  4. data/lib/origen_testers/atp.rb +95 -0
  5. data/lib/origen_testers/atp/ast/extractor.rb +26 -0
  6. data/lib/origen_testers/atp/ast/node.rb +147 -0
  7. data/lib/origen_testers/atp/flow.rb +920 -0
  8. data/lib/origen_testers/atp/flow_api.rb +56 -0
  9. data/lib/origen_testers/atp/formatter.rb +25 -0
  10. data/lib/origen_testers/atp/formatters/basic.rb +32 -0
  11. data/lib/origen_testers/atp/formatters/datalog.rb +65 -0
  12. data/lib/origen_testers/atp/parser.rb +26 -0
  13. data/lib/origen_testers/atp/processor.rb +73 -0
  14. data/lib/origen_testers/atp/processors/add_ids.rb +45 -0
  15. data/lib/origen_testers/atp/processors/add_set_result.rb +22 -0
  16. data/lib/origen_testers/atp/processors/adjacent_if_combiner.rb +100 -0
  17. data/lib/origen_testers/atp/processors/append_to.rb +27 -0
  18. data/lib/origen_testers/atp/processors/apply_post_group_actions.rb +50 -0
  19. data/lib/origen_testers/atp/processors/condition.rb +179 -0
  20. data/lib/origen_testers/atp/processors/continue_implementer.rb +35 -0
  21. data/lib/origen_testers/atp/processors/else_remover.rb +31 -0
  22. data/lib/origen_testers/atp/processors/empty_branch_remover.rb +17 -0
  23. data/lib/origen_testers/atp/processors/extract_set_flags.rb +18 -0
  24. data/lib/origen_testers/atp/processors/flag_optimizer.rb +234 -0
  25. data/lib/origen_testers/atp/processors/flattener.rb +58 -0
  26. data/lib/origen_testers/atp/processors/flow_id.rb +46 -0
  27. data/lib/origen_testers/atp/processors/marshal.rb +33 -0
  28. data/lib/origen_testers/atp/processors/on_pass_fail_remover.rb +39 -0
  29. data/lib/origen_testers/atp/processors/one_flag_per_test.rb +79 -0
  30. data/lib/origen_testers/atp/processors/pre_cleaner.rb +65 -0
  31. data/lib/origen_testers/atp/processors/redundant_condition_remover.rb +28 -0
  32. data/lib/origen_testers/atp/processors/relationship.rb +199 -0
  33. data/lib/origen_testers/atp/processors/sub_flow_remover.rb +10 -0
  34. data/lib/origen_testers/atp/program.rb +48 -0
  35. data/lib/origen_testers/atp/runner.rb +234 -0
  36. data/lib/origen_testers/atp/validator.rb +53 -0
  37. data/lib/origen_testers/atp/validators/condition.rb +4 -0
  38. data/lib/origen_testers/atp/validators/duplicate_ids.rb +32 -0
  39. data/lib/origen_testers/atp/validators/flags.rb +59 -0
  40. data/lib/origen_testers/atp/validators/jobs.rb +55 -0
  41. data/lib/origen_testers/atp/validators/missing_ids.rb +63 -0
  42. data/lib/origen_testers/atp_deprecation.rb +2 -0
  43. metadata +62 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fe9e0d79136d4c5a7a16b3e73e098ac9069c0aa0d4efb70e4e9c93bb1b1d01d7
4
- data.tar.gz: 115289be29ed7e600ee5e331a61649e05f649131956214bebd81a6afb963a0d7
3
+ metadata.gz: 357c39883c8fa52a9499bdbcbc5ca950964c5b55f64de81c24f2cecd439abf7a
4
+ data.tar.gz: 1af0e55f758ccb891071160a021c120836881cf75cc614d106fd054f18966e91
5
5
  SHA512:
6
- metadata.gz: 13b81ab2f3f0c642ba283399fbc39df7f1fcf7f982995f53ae75ef4acef6b060ca196bec8ea9571b65a548831a712eba6653b6eadcd52b5672fd84cad91ff051
7
- data.tar.gz: 1ee85240a3d5c559dff37c61f12110f02a1a8228fc36f828911fe7d13c79cfc12fff72d49ead48f7b0ca33ee24fc3263f2f4f357abfd014aad3bb418bb12b0be
6
+ metadata.gz: ed5f3368c365c91656b1dfa05916e997ecf21009ceba99d96e35258102144edf812c6ad2ee1c341ef2f00d87d7866cedd09fadccb1bbefc8159833b175d598eb
7
+ data.tar.gz: 542144dfbbbdbf298f5ad599cd90a48a88ce09f9c6976161f62a789f063cda5bd53ea5c4239df8307a07d2d00c4294874c4b074e68d1946b62cad4b13d01e789
@@ -1,8 +1,7 @@
1
1
  module OrigenTesters
2
2
  MAJOR = 0
3
- MINOR = 21
3
+ MINOR = 30
4
4
  BUGFIX = 0
5
5
  DEV = nil
6
-
7
6
  VERSION = [MAJOR, MINOR, BUGFIX].join(".") + (DEV ? ".pre#{DEV}" : '')
8
7
  end
@@ -3,7 +3,6 @@ require_relative '../config/application.rb'
3
3
 
4
4
  require 'active_support/concern'
5
5
  require 'require_all'
6
- require 'atp'
7
6
  require 'pathname'
8
7
  require 'origen_testers/origen_ext/generator/pattern'
9
8
  require 'origen_testers/origen_ext/generator/flow'
@@ -24,6 +23,7 @@ module OrigenTesters
24
23
  autoload :Flow, 'origen_testers/flow'
25
24
  autoload :NoInterface, 'origen_testers/no_interface'
26
25
  autoload :MemoryStyle, 'origen_testers/memory_style'
26
+ autoload :ATP, 'origen_testers/atp'
27
27
 
28
28
  # not yet autoload :Time, 'origen_testers/time'
29
29
 
@@ -39,6 +39,7 @@ module OrigenTesters
39
39
  end
40
40
  end
41
41
  end
42
+ autoload :ATP, 'origen_testers/atp_deprecation'
42
43
 
43
44
  require 'origen_testers/igxl_based_tester'
44
45
  require 'origen_testers/smartest_based_tester'
@@ -0,0 +1,95 @@
1
+ module OrigenTesters::ATP
2
+ autoload :Program, 'origen_testers/atp/program'
3
+ autoload :Flow, 'origen_testers/atp/flow'
4
+ autoload :Processor, 'origen_testers/atp/processor'
5
+ autoload :Validator, 'origen_testers/atp/validator'
6
+ autoload :Runner, 'origen_testers/atp/runner'
7
+ autoload :Formatter, 'origen_testers/atp/formatter'
8
+ autoload :Parser, 'origen_testers/atp/parser'
9
+ autoload :FlowAPI, 'origen_testers/atp/flow_api'
10
+
11
+ module AST
12
+ autoload :Node, 'origen_testers/atp/ast/node'
13
+ autoload :Extractor, 'origen_testers/atp/ast/extractor'
14
+
15
+ # This is a shim to help backwards compatibility with ATP v0
16
+ module Builder
17
+ class LazyObject < ::BasicObject
18
+ def initialize(&callable)
19
+ @callable = callable
20
+ end
21
+
22
+ def __target_object__
23
+ @__target_object__ ||= @callable.call
24
+ end
25
+
26
+ def method_missing(method_name, *args, &block)
27
+ __target_object__.send(method_name, *args, &block)
28
+ end
29
+ end
30
+
31
+ # Some trickery to lazy load this to fire a deprecation warning if an app references it
32
+ CONDITION_KEYS ||= LazyObject.new do
33
+ Origen.log.deprecate 'ATP::AST::Builder::CONDITION_KEYS is frozen and is no longer maintained, consider switching to ATP::Flow::CONDITION_KEYS.keys for similar functionality'
34
+ [:if_enabled, :enabled, :enable_flag, :enable, :if_enable, :unless_enabled, :not_enabled,
35
+ :disabled, :disable, :unless_enable, :if_failed, :unless_passed, :failed, :if_passed,
36
+ :unless_failed, :passed, :if_ran, :if_executed, :unless_ran, :unless_executed, :job,
37
+ :jobs, :if_job, :if_jobs, :unless_job, :unless_jobs, :if_any_failed, :unless_all_passed,
38
+ :if_all_failed, :unless_any_passed, :if_any_passed, :unless_all_failed, :if_all_passed,
39
+ :unless_any_failed, :if_flag, :unless_flag, :whenever, :whenever_all, :whenever_any]
40
+ end
41
+ end
42
+ end
43
+
44
+ # Processors actually modify the AST to clean and optimize the user input
45
+ # and to implement the flow control API
46
+ module Processors
47
+ autoload :Condition, 'origen_testers/atp/processors/condition'
48
+ autoload :Relationship, 'origen_testers/atp/processors/relationship'
49
+ autoload :PreCleaner, 'origen_testers/atp/processors/pre_cleaner'
50
+ autoload :Marshal, 'origen_testers/atp/processors/marshal'
51
+ autoload :AddIDs, 'origen_testers/atp/processors/add_ids'
52
+ autoload :AddSetResult, 'origen_testers/atp/processors/add_set_result'
53
+ autoload :FlowID, 'origen_testers/atp/processors/flow_id'
54
+ autoload :EmptyBranchRemover, 'origen_testers/atp/processors/empty_branch_remover'
55
+ autoload :AppendTo, 'origen_testers/atp/processors/append_to'
56
+ autoload :Flattener, 'origen_testers/atp/processors/flattener'
57
+ autoload :RedundantConditionRemover, 'origen_testers/atp/processors/redundant_condition_remover'
58
+ autoload :ElseRemover, 'origen_testers/atp/processors/else_remover'
59
+ autoload :OnPassFailRemover, 'origen_testers/atp/processors/on_pass_fail_remover'
60
+ autoload :ApplyPostGroupActions, 'origen_testers/atp/processors/apply_post_group_actions'
61
+ autoload :OneFlagPerTest, 'origen_testers/atp/processors/one_flag_per_test'
62
+ autoload :FlagOptimizer, 'origen_testers/atp/processors/flag_optimizer'
63
+ autoload :AdjacentIfCombiner, 'origen_testers/atp/processors/adjacent_if_combiner'
64
+ autoload :ContinueImplementer, 'origen_testers/atp/processors/continue_implementer'
65
+ autoload :ExtractSetFlags, 'origen_testers/atp/processors/extract_set_flags'
66
+ autoload :SubFlowRemover, 'origen_testers/atp/processors/sub_flow_remover'
67
+ end
68
+
69
+ # Summarizers extract summary data from the given AST
70
+ module Summarizers
71
+ end
72
+
73
+ # Validators are run on the processed AST to check it for common errors or
74
+ # logical issues that will prevent it being rendered to a test program format
75
+ module Validators
76
+ autoload :DuplicateIDs, 'origen_testers/atp/validators/duplicate_ids'
77
+ autoload :MissingIDs, 'origen_testers/atp/validators/missing_ids'
78
+ autoload :Condition, 'origen_testers/atp/validators/condition'
79
+ autoload :Jobs, 'origen_testers/atp/validators/jobs'
80
+ autoload :Flags, 'origen_testers/atp/validators/flags'
81
+ end
82
+
83
+ # Formatters are run on the processed AST to display the flow or to render
84
+ # it to a different format
85
+ module Formatters
86
+ autoload :Basic, 'origen_testers/atp/formatters/basic'
87
+ autoload :Datalog, 'origen_testers/atp/formatters/datalog'
88
+ end
89
+
90
+ # Maintains a unique ID counter to ensure that all nodes get a unique ID
91
+ def self.next_id
92
+ @next_id ||= 0
93
+ @next_id += 1
94
+ end
95
+ end
@@ -0,0 +1,26 @@
1
+ require 'ast'
2
+ module OrigenTesters::ATP
3
+ module AST
4
+ class Extractor
5
+ include ::AST::Processor::Mixin
6
+
7
+ attr_reader :types
8
+ attr_reader :results
9
+
10
+ def process(node, types = nil)
11
+ if types
12
+ @types = types
13
+ @results = []
14
+ # node = AST::Node.new(:wrapper, node) unless node.respond_to?(:to_ast)
15
+ end
16
+ super(node) if node.respond_to?(:to_ast)
17
+ results
18
+ end
19
+
20
+ def handler_missing(node)
21
+ @results << node if types.include?(node.type)
22
+ process_all(node.children)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,147 @@
1
+ require 'ast'
2
+ module OrigenTesters::ATP
3
+ module AST
4
+ class Node < ::AST::Node
5
+ attr_reader :file, :line_number, :description, :properties
6
+ attr_accessor :id
7
+
8
+ def initialize(type, children = [], properties = {})
9
+ @properties = properties
10
+ # Always use strings instead of symbols in the AST, makes serializing
11
+ # back and forward to a string easier
12
+ children = children.map { |c| c.is_a?(Symbol) ? c.to_s : c }
13
+ super type, children, properties
14
+ end
15
+
16
+ def _dump(depth)
17
+ # this strips the @strip information from the instance
18
+ # d = { type: type, children: children, properties: properties }
19
+ d = { klass: self.class,
20
+ id: id,
21
+ file: file,
22
+ line_number: line_number,
23
+ description: description,
24
+ type: type,
25
+ children: Processors::Marshal.new.process_all(children),
26
+ properties: properties
27
+ }
28
+ Marshal.dump(d, depth)
29
+ end
30
+
31
+ def self._load(str)
32
+ d = Marshal.load(str)
33
+ p = d[:properties]
34
+ p[:id] = d[:id]
35
+ p[:file] = d[:file]
36
+ p[:line_number] = d[:line_number]
37
+ p[:description] = d[:description]
38
+ n = d[:klass].new(d[:type], d[:children], p)
39
+ n
40
+ end
41
+
42
+ def source
43
+ if file
44
+ "#{file}:#{line_number}"
45
+ else
46
+ '<Sorry, lost the source file info, please include an example if you report as a bug>'
47
+ end
48
+ end
49
+
50
+ # Returns true if the node carries source file data, retrieve it via the source method
51
+ def has_source?
52
+ !!file
53
+ end
54
+
55
+ # Create a new node from the given S-expression (a string)
56
+ def self.from_sexp(sexp)
57
+ @parser ||= Parser.new
58
+ @parser.string_to_ast(sexp)
59
+ end
60
+
61
+ # Adds an empty node of the given type to the children unless another
62
+ # node of the same type is already present
63
+ def ensure_node_present(type, *child_nodes)
64
+ if children.any? { |n| n.type == type }
65
+ self
66
+ else
67
+ if !child_nodes.empty?
68
+ node = updated(type, child_nodes)
69
+ else
70
+ node = updated(type, [])
71
+ end
72
+ updated(nil, children + [node])
73
+ end
74
+ end
75
+
76
+ # Returns the value at the root of an AST node like this:
77
+ #
78
+ # node # => (module-def
79
+ # (module-name
80
+ # (SCALAR-ID "Instrument"))
81
+ #
82
+ # node.value # => "Instrument"
83
+ #
84
+ # No error checking is done and the caller is responsible for calling
85
+ # this only on compatible nodes
86
+ def value
87
+ val = children.first
88
+ val = val.children.first while val.respond_to?(:children)
89
+ val
90
+ end
91
+
92
+ # Add the given nodes to the children
93
+ def add(*nodes)
94
+ updated(nil, children + nodes)
95
+ end
96
+
97
+ # Remove the given nodes (or types) from the children
98
+ def remove(*nodes)
99
+ nodes = nodes.map do |node|
100
+ if node.is_a?(Symbol)
101
+ find_all(node)
102
+ else
103
+ node
104
+ end
105
+ end.flatten.compact
106
+ updated(nil, children - nodes)
107
+ end
108
+
109
+ # Returns the first child node of the given type(s) that is found
110
+ def find(*types)
111
+ children.find { |c| types.include?(c.try(:type)) }
112
+ end
113
+
114
+ # Returns an array containing all child nodes of the given type(s), by default only considering
115
+ # the immediate children of the node on which this was called.
116
+ #
117
+ # To find all children of the given type by recursively searching through all child nodes, pass
118
+ # recursive: true when calling this method.
119
+ def find_all(*types)
120
+ options = types.pop if types.last.is_a?(Hash)
121
+ options ||= {}
122
+ if options[:recursive]
123
+ Extractor.new.process(self, types)
124
+ else
125
+ children.select { |c| types.include?(c.try(:type)) }
126
+ end
127
+ end
128
+
129
+ # Returns an array containing all flags which are set within the given node
130
+ def set_flags
131
+ Processors::ExtractSetFlags.new.run(self)
132
+ end
133
+
134
+ # Returns a copy of node with any sub-flow nodes removed
135
+ def excluding_sub_flows
136
+ Processors::SubFlowRemover.new.run(self)
137
+ end
138
+
139
+ # Returns true if the node contains any nodes of the given type(s) or if any
140
+ # of its children do.
141
+ # To consider only direct children of this node use: node.find_all(*types).empty?
142
+ def contains?(*types)
143
+ !Extractor.new.process(self, types).empty?
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,920 @@
1
+ module OrigenTesters::ATP
2
+ # Implements the main user API for building and interacting
3
+ # with an abstract test program
4
+ class Flow
5
+ attr_reader :program, :name
6
+
7
+ CONDITION_KEYS = {
8
+ if_enabled: :if_enabled,
9
+ if_enable: :if_enabled,
10
+ enabled: :if_enabled,
11
+ enable_flag: :if_enabled,
12
+ enable: :if_enabled,
13
+
14
+ unless_enabled: :unless_enabled,
15
+ not_enabled: :unless_enabled,
16
+ disabled: :unless_enabled,
17
+ disable: :unless_enabled,
18
+ unless_enable: :unless_enabled,
19
+
20
+ if_failed: :if_failed,
21
+ unless_passed: :if_failed,
22
+ failed: :if_failed,
23
+
24
+ if_passed: :if_passed,
25
+ unless_failed: :if_passed,
26
+ passed: :if_passed,
27
+
28
+ if_any_failed: :if_any_failed,
29
+ unless_all_passed: :if_any_failed,
30
+
31
+ if_all_failed: :if_all_failed,
32
+ unless_any_passed: :if_all_failed,
33
+
34
+ if_any_passed: :if_any_passed,
35
+ unless_all_failed: :if_any_passed,
36
+
37
+ if_all_passed: :if_all_passed,
38
+ unless_any_failed: :if_all_passed,
39
+
40
+ if_ran: :if_ran,
41
+ if_executed: :if_ran,
42
+
43
+ unless_ran: :unless_ran,
44
+ unless_executed: :unless_ran,
45
+
46
+ job: :if_job,
47
+ jobs: :if_job,
48
+ if_job: :if_job,
49
+ if_jobs: :if_job,
50
+
51
+ unless_job: :unless_job,
52
+ unless_jobs: :unless_job,
53
+
54
+ if_flag: :if_flag,
55
+
56
+ unless_flag: :unless_flag,
57
+
58
+ whenever: :whenever,
59
+ whenever_all: :whenever_all,
60
+ whenever_any: :whenever_any,
61
+
62
+ group: :group
63
+ }
64
+
65
+ CONDITION_NODE_TYPES = CONDITION_KEYS.values.uniq
66
+
67
+ RELATIONAL_OPERATORS = [:eq, :ne, :lt, :le, :gt, :ge]
68
+
69
+ def initialize(program, name = nil, options = {})
70
+ name, options = nil, name if name.is_a?(Hash)
71
+ @source_file = []
72
+ @source_line_number = []
73
+ @description = []
74
+ @program = program
75
+ @name = name
76
+ extract_meta!(options) do
77
+ @pipeline = [n1(:flow, n1(:name, name))]
78
+ end
79
+ end
80
+
81
+ # @api private
82
+ def marshal_dump
83
+ [@name, @program, Processors::Marshal.new.process(raw)]
84
+ end
85
+
86
+ # @api private
87
+ def marshal_load(array)
88
+ @name, @program, raw = array
89
+ @pipeline = [raw]
90
+ end
91
+
92
+ # Returns the raw AST
93
+ def raw
94
+ n = nil
95
+ @pipeline.reverse_each do |node|
96
+ if n
97
+ n = node.updated(nil, node.children + [n])
98
+ else
99
+ n = node
100
+ end
101
+ end
102
+ n
103
+ end
104
+
105
+ # Returns a processed/optimized AST, this is the one that should be
106
+ # used to build and represent the given test flow
107
+ def ast(options = {})
108
+ options = {
109
+ apply_relationships: true,
110
+ # Supply a unique ID to append to all IDs
111
+ unique_id: nil,
112
+ # Set to :smt, or :igxl
113
+ optimization: :runner,
114
+ # When true, will remove set_result nodes in an on_fail branch which contains a continue
115
+ implement_continue: true,
116
+ # When false, this will not optimize the use of a flag by nesting a dependent test within
117
+ # the parent test's on_fail branch if the on_fail contains a continue
118
+ optimize_flags_when_continue: true,
119
+ # These options are not intended for application use, but provide the ability to
120
+ # turn off certain processors during test cases
121
+ add_ids: true,
122
+ optimize_flags: true,
123
+ one_flag_per_test: true,
124
+ include_sub_flows: true
125
+ }.merge(options)
126
+ ###############################################################################
127
+ ## Common pre-processing and validation
128
+ ###############################################################################
129
+ ast = Processors::PreCleaner.new.run(raw)
130
+ Validators::DuplicateIDs.new(self).run(ast)
131
+ Validators::MissingIDs.new(self).run(ast)
132
+ Validators::Jobs.new(self).run(ast)
133
+ Validators::Flags.new(self).run(ast)
134
+ # Ensure everything has an ID, this helps later if condition nodes need to be generated
135
+ ast = Processors::AddIDs.new.run(ast) if options[:add_ids]
136
+ ast = Processors::FlowID.new.run(ast, options[:unique_id]) if options[:unique_id]
137
+ ast = Processors::SubFlowRemover.new.run(ast) unless options[:include_sub_flows]
138
+
139
+ ###############################################################################
140
+ ## Optimization for a C-like flow target, e.g. V93K
141
+ ###############################################################################
142
+ if options[:optimization] == :smt || options[:optimization] == :runner
143
+ # This applies all the relationships by setting flags in the referenced test and
144
+ # changing all if_passed/failed type nodes to if_flag type nodes
145
+ ast = Processors::Relationship.new.run(ast) if options[:apply_relationships]
146
+ ast = Processors::Condition.new.run(ast)
147
+ unless options[:optimization] == :runner
148
+ ast = Processors::ContinueImplementer.new.run(ast) if options[:implement_continue]
149
+ end
150
+ if options[:optimize_flags]
151
+ ast = Processors::FlagOptimizer.new.run(ast, optimize_when_continue: options[:optimize_flags_when_continue])
152
+ end
153
+ ast = Processors::AdjacentIfCombiner.new.run(ast)
154
+
155
+ ###############################################################################
156
+ ## Optimization for a row-based target, e.g. UltraFLEX
157
+ ###############################################################################
158
+ elsif options[:optimization] == :igxl
159
+ # Un-nest everything embedded in else nodes
160
+ ast = Processors::ElseRemover.new.run(ast)
161
+ # Un-nest everything embedded in on_pass/fail nodes except for binning and
162
+ # flag setting
163
+ ast = Processors::OnPassFailRemover.new.run(ast)
164
+ # This applies all the relationships by setting flags in the referenced test and
165
+ # changing all if_passed/failed type nodes to if_flag type nodes
166
+ ast = Processors::Relationship.new.run(ast) if options[:apply_relationships]
167
+ ast = Processors::Condition.new.run(ast)
168
+ ast = Processors::ApplyPostGroupActions.new.run(ast)
169
+ ast = Processors::OneFlagPerTest.new.run(ast) if options[:one_flag_per_test]
170
+ ast = Processors::RedundantConditionRemover.new.run(ast)
171
+
172
+ ###############################################################################
173
+ ## Not currently used, more of a test case
174
+ ###############################################################################
175
+ elsif options[:optimization] == :flat
176
+ # Un-nest everything embedded in else nodes
177
+ ast = Processors::ElseRemover.new.run(ast)
178
+ # Un-nest everything embedded in on_pass/fail nodes except for binning and
179
+ # flag setting
180
+ ast = Processors::OnPassFailRemover.new.run(ast)
181
+ ast = Processors::Condition.new.run(ast)
182
+ ast = Processors::Flattener.new.run(ast)
183
+
184
+ ###############################################################################
185
+ ## Default Optimization
186
+ ###############################################################################
187
+ else
188
+ ast = Processors::Condition.new.run(ast)
189
+ end
190
+
191
+ ###############################################################################
192
+ ## Common cleanup
193
+ ###############################################################################
194
+ # Removes any empty on_pass and on_fail branches
195
+ ast = Processors::EmptyBranchRemover.new.run(ast)
196
+ ast
197
+ end
198
+
199
+ # Indicate the that given flags should be considered volatile (can change at any time), which will
200
+ # prevent them from been touched by the optimization algorithms
201
+ def volatile(*flags)
202
+ options = flags.pop if flags.last.is_a?(Hash)
203
+ flags = flags.flatten
204
+ @pipeline[0] = add_volatile_flags(@pipeline[0], flags)
205
+ end
206
+
207
+ # Record a description for a bin number
208
+ def describe_bin(number, description, options = {})
209
+ @pipeline[0] = add_bin_description(@pipeline[0], number, description, type: :hard)
210
+ end
211
+
212
+ # Record a description for a softbin number
213
+ def describe_soft_bin(number, description, options = {})
214
+ @pipeline[0] = add_bin_description(@pipeline[0], number, description, type: :soft)
215
+ end
216
+ alias_method :describe_softbin, :describe_soft_bin
217
+
218
+ # Group all tests generated within the given block
219
+ #
220
+ # @example
221
+ # flow.group "RAM Tests" do
222
+ # flow.test ...
223
+ # flow.test ...
224
+ # end
225
+ def group(name, options = {})
226
+ extract_meta!(options) do
227
+ apply_conditions(options) do
228
+ children = [n1(:name, name)]
229
+ children << id(options[:id]) if options[:id]
230
+ children << on_fail(options[:on_fail]) if options[:on_fail]
231
+ children << on_pass(options[:on_pass]) if options[:on_pass]
232
+ g = n(:group, children)
233
+ append_to(g) { yield }
234
+ end
235
+ end
236
+ end
237
+
238
+ # Add a test line to the flow
239
+ #
240
+ # @param [String, Symbol] the name of the test
241
+ # @param [Hash] options a hash to describe the test's attributes
242
+ # @option options [Symbol] :id A unique test ID
243
+ # @option options [String] :description A description of what the test does, usually formatted in markdown
244
+ # @option options [Hash] :on_fail What action to take if the test fails, e.g. assign a bin
245
+ # @option options [Hash] :on_pass What action to take if the test passes
246
+ # @option options [Hash] :conditions What conditions must be met to execute the test
247
+ def test(instance, options = {})
248
+ extract_meta!(options) do
249
+ apply_conditions(options) do
250
+ if options[:on_fail].is_a?(Proc)
251
+ before_on_fail = options.delete(:on_fail)
252
+ end
253
+ if options[:on_pass].is_a?(Proc)
254
+ before_on_pass = options.delete(:on_pass)
255
+ end
256
+ # Allows any continue, bin, or soft bin argument passed in at the options top-level to be assumed
257
+ # to be the action to take if the test fails
258
+ if b = options.delete(:bin)
259
+ options[:on_fail] ||= {}
260
+ options[:on_fail][:bin] = b
261
+ end
262
+ if b = options.delete(:bin_description)
263
+ options[:on_fail] ||= {}
264
+ options[:on_fail][:bin_description] = b
265
+ end
266
+ if b = options.delete(:bin_attrs)
267
+ options[:on_fail] ||= {}
268
+ options[:on_fail][:bin_attrs] = b
269
+ end
270
+ if b = options.delete(:softbin) || b = options.delete(:sbin) || b = options.delete(:soft_bin)
271
+ options[:on_fail] ||= {}
272
+ options[:on_fail][:softbin] = b
273
+ end
274
+ if b = options.delete(:softbin_description) || options.delete(:sbin_description) || options.delete(:soft_bin_description)
275
+ options[:on_fail] ||= {}
276
+ options[:on_fail][:softbin_description] = b
277
+ end
278
+ if options.delete(:continue)
279
+ options[:on_fail] ||= {}
280
+ options[:on_fail][:continue] = true
281
+ end
282
+ if options.key?(:delayed)
283
+ options[:on_fail] ||= {}
284
+ options[:on_fail][:delayed] = options.delete(:delayed)
285
+ end
286
+ if f = options.delete(:flag_pass)
287
+ options[:on_pass] ||= {}
288
+ options[:on_pass][:set_flag] = f
289
+ end
290
+ if f = options.delete(:flag_fail)
291
+ options[:on_fail] ||= {}
292
+ options[:on_fail][:set_flag] = f
293
+ end
294
+
295
+ children = [n1(:object, instance)]
296
+
297
+ name = (options[:name] || options[:tname] || options[:test_name])
298
+ unless name
299
+ [:name, :tname, :test_name].each do |m|
300
+ name ||= instance.respond_to?(m) ? instance.send(m) : nil
301
+ end
302
+ end
303
+ children << n1(:name, name) if name
304
+
305
+ num = (options[:number] || options[:num] || options[:tnum] || options[:test_number])
306
+ unless num
307
+ [:number, :num, :tnum, :test_number].each do |m|
308
+ num ||= instance.respond_to?(m) ? instance.send(m) : nil
309
+ end
310
+ end
311
+ children << number(num) if num
312
+
313
+ children << id(options[:id]) if options[:id]
314
+
315
+ if levels = options[:level] || options[:levels]
316
+ levels = [levels] unless levels.is_a?(Array)
317
+ levels.each do |l|
318
+ children << level(l[:name], l[:value], l[:unit] || l[:units])
319
+ end
320
+ end
321
+
322
+ lims = options[:limit] || options[:limits]
323
+ if lims || options[:lo] || options[:low] || options[:hi] || options[:high]
324
+ if lims == :none || lims == 'none'
325
+ children << n0(:nolimits)
326
+ else
327
+ lims = Array(lims) unless lims.is_a?(Array)
328
+ if lo = options[:lo] || options[:low]
329
+ lims << { value: lo, rule: :gte }
330
+ end
331
+ if hi = options[:hi] || options[:high]
332
+ lims << { value: hi, rule: :lte }
333
+ end
334
+ lims.each do |l|
335
+ if l.is_a?(Hash)
336
+ children << n(:limit, [l[:value], l[:rule], l[:unit] || l[:units], l[:selector]])
337
+ end
338
+ end
339
+ end
340
+ end
341
+
342
+ if pins = options[:pin] || options[:pins]
343
+ pins = [pins] unless pins.is_a?(Array)
344
+ pins.each do |p|
345
+ if p.is_a?(Hash)
346
+ children << pin(p[:name])
347
+ else
348
+ children << pin(p)
349
+ end
350
+ end
351
+ end
352
+
353
+ if pats = options[:pattern] || options[:patterns]
354
+ pats = [pats] unless pats.is_a?(Array)
355
+ pats.each do |p|
356
+ if p.is_a?(Hash)
357
+ children << pattern(p[:name], p[:path])
358
+ else
359
+ children << pattern(p)
360
+ end
361
+ end
362
+ end
363
+
364
+ if options[:meta]
365
+ attrs = []
366
+ options[:meta].each { |k, v| attrs << attribute(k, v) }
367
+ children << n(:meta, attrs)
368
+ end
369
+
370
+ if subs = options[:sub_test] || options[:sub_tests]
371
+ subs = [subs] unless subs.is_a?(Array)
372
+ subs.each do |s|
373
+ children << s.updated(:sub_test, nil)
374
+ end
375
+ end
376
+
377
+ if before_on_fail
378
+ on_fail_node = on_fail(before_on_fail)
379
+ if options[:on_fail]
380
+ on_fail_node = on_fail_node.updated(nil, on_fail_node.children + on_fail(options[:on_fail]).children)
381
+ end
382
+ children << on_fail_node
383
+ else
384
+ children << on_fail(options[:on_fail]) if options[:on_fail]
385
+ end
386
+
387
+ if before_on_pass
388
+ on_pass_node = on_pass(before_on_pass)
389
+ if options[:on_pass]
390
+ on_pass_node = on_pass_node.updated(nil, on_pass_node.children + on_pass(options[:on_pass]).children)
391
+ end
392
+ children << on_pass_node
393
+ else
394
+ children << on_pass(options[:on_pass]) if options[:on_pass]
395
+ end
396
+
397
+ save_conditions
398
+ n(:test, children)
399
+ end
400
+ end
401
+ end
402
+
403
+ # Equivalent to calling test, but returns a sub_test node instead of adding it to the flow.
404
+ #
405
+ # This is a helper to create sub_tests for inclusion in a top-level test node.
406
+ def sub_test(instance, options = {})
407
+ temp = append_to(n0(:temp)) { test(instance, options) }
408
+ temp.children.first.updated(:sub_test, nil)
409
+ end
410
+
411
+ def sub_flow(flow_node, options = {})
412
+ name, *children = *flow_node
413
+ if options[:path]
414
+ children = [name] + [n1(:path, options[:path])] + children
415
+ else
416
+ children = [name] + children
417
+ end
418
+ apply_conditions(options) do
419
+ flow_node.updated(:sub_flow, children)
420
+ end
421
+ end
422
+
423
+ def bin(number, options = {})
424
+ if number.is_a?(Hash)
425
+ fail 'The bin number must be passed as the first argument'
426
+ end
427
+ options[:bin_description] ||= options.delete(:description)
428
+ extract_meta!(options) do
429
+ apply_conditions(options) do
430
+ options[:type] ||= :fail
431
+ options[:bin] = number
432
+ options[:softbin] ||= options[:soft_bin] || options[:sbin]
433
+ set_result(options[:type], options)
434
+ end
435
+ end
436
+ end
437
+
438
+ def pass(number, options = {})
439
+ if number.is_a?(Hash)
440
+ fail 'The bin number must be passed as the first argument'
441
+ end
442
+ options[:type] = :pass
443
+ bin(number, options)
444
+ end
445
+
446
+ def cz(instance, cz_setup, options = {})
447
+ extract_meta!(options) do
448
+ apply_conditions(options) do
449
+ node = n1(:cz, cz_setup)
450
+ append_to(node) { test(instance, options) }
451
+ end
452
+ end
453
+ end
454
+ alias_method :characterize, :cz
455
+
456
+ # Append a log message line to the flow
457
+ def log(message, options = {})
458
+ extract_meta!(options) do
459
+ apply_conditions(options) do
460
+ n1(:log, message.to_s)
461
+ end
462
+ end
463
+ end
464
+
465
+ # Enable a flow control variable
466
+ def enable(var, options = {})
467
+ extract_meta!(options) do
468
+ apply_conditions(options) do
469
+ n1(:enable, var)
470
+ end
471
+ end
472
+ end
473
+
474
+ # Disable a flow control variable
475
+ def disable(var, options = {})
476
+ extract_meta!(options) do
477
+ apply_conditions(options) do
478
+ n1(:disable, var)
479
+ end
480
+ end
481
+ end
482
+
483
+ def set_flag(flag, options = {})
484
+ extract_meta!(options) do
485
+ apply_conditions(options) do
486
+ set_flag_node(flag)
487
+ end
488
+ end
489
+ end
490
+
491
+ def set(var, val, options = {})
492
+ extract_meta!(options) do
493
+ apply_conditions(options) do
494
+ n2(:set, var, val)
495
+ end
496
+ end
497
+ end
498
+
499
+ # Insert explicitly rendered content in to the flow
500
+ def render(str, options = {})
501
+ extract_meta!(options) do
502
+ apply_conditions(options) do
503
+ n1(:render, str)
504
+ end
505
+ end
506
+ end
507
+
508
+ def continue(options = {})
509
+ extract_meta!(options) do
510
+ apply_conditions(options) do
511
+ n0(:continue)
512
+ end
513
+ end
514
+ end
515
+
516
+ # Execute the given flow in the console
517
+ def run(options = {})
518
+ Formatters::Datalog.run_and_format(ast, options)
519
+ nil
520
+ end
521
+
522
+ # Returns true if the test context generated from the supplied options + existing condition
523
+ # wrappers, is different from that which was applied to the previous test.
524
+ def context_changed?(options)
525
+ options[:_dont_delete_conditions_] = true
526
+ last_conditions != clean_conditions(open_conditions + [extract_conditions(options)])
527
+ end
528
+
529
+ def whenever(*expressions, &block)
530
+ if expressions.last.is_a?(Hash)
531
+ options = expressions.pop
532
+ else
533
+ options = {}
534
+ end
535
+ flow_control_method(:whenever, expressions, options, &block)
536
+ end
537
+
538
+ def loop(*args, &block)
539
+ unless args[0].keys.include?(:from) && args[0].keys.include?(:to) && args[0].keys.include?(:step)
540
+ fail 'Loop must specify :from, :to, :step'
541
+ end
542
+ extract_meta!(options) do
543
+ apply_conditions(options) do
544
+ # always pass 4-element array to loop node to simplify downstream parser
545
+ # last element, 'var', will be nil if not specified by loop call
546
+ params = [args[0][:from], args[0][:to], args[0][:step], args[0][:var]]
547
+
548
+ node = n(:loop, params)
549
+ node = append_to(node) { block.call }
550
+ node
551
+ end
552
+ end
553
+ end
554
+
555
+ RELATIONAL_OPERATORS.each do |method|
556
+ define_method method do |*args, &block|
557
+ options = args.pop if args.last.is_a?(Hash)
558
+ unless args.size == 2
559
+ fail "Format for relational operation must match: ':<opertor>(var1, var2)'"
560
+ end
561
+ n2(method.to_sym, args[0], args[1])
562
+ end unless method_defined?(method)
563
+ end
564
+
565
+ # Define handlers for all of the flow control block methods, unless a custom one has already
566
+ # been defined above
567
+ CONDITION_KEYS.keys.each do |method|
568
+ define_method method do |*flags, &block|
569
+ if flags.last.is_a?(Hash)
570
+ options = flags.pop
571
+ else
572
+ options = {}
573
+ end
574
+ flags = flags.first if flags.size == 1
575
+ # Legacy option provided by OrigenTesters that permits override of a block enable method by passing
576
+ # an :or option with a true value
577
+ if (CONDITION_KEYS[method] == :if_enabled || CONDITION_KEYS[method] || :unless_enabled) && options[:or]
578
+ block.call
579
+ else
580
+ flow_control_method(CONDITION_KEYS[method], flags, options, &block)
581
+ end
582
+ end unless method_defined?(method)
583
+ end
584
+
585
+ def inspect
586
+ "<OrigenTesters::ATP::Flow:#{object_id} #{name}>"
587
+ end
588
+
589
+ def ids(options = {})
590
+ OrigenTesters::ATP::AST::Extractor.new.process(raw, [:id]).map { |node| node.to_a[0] }
591
+ end
592
+
593
+ private
594
+
595
+ def description
596
+ @description.last
597
+ end
598
+
599
+ def source_file
600
+ @source_file.last
601
+ end
602
+
603
+ def source_line_number
604
+ @source_line_number.last
605
+ end
606
+
607
+ def flow_control_method(name, flag, options = {}, &block)
608
+ extract_meta!(options) do
609
+ if flag.is_a?(Array)
610
+ if name == :if_passed
611
+ fail 'if_passed only accepts one ID, use if_any_passed or if_all_passed for multiple IDs'
612
+ end
613
+ if name == :if_failed
614
+ fail 'if_failed only accepts one ID, use if_any_failed or if_all_failed for multiple IDs'
615
+ end
616
+ end
617
+ apply_conditions(options) do
618
+ if block
619
+ node = n1(name, flag)
620
+ open_conditions << [name, flag]
621
+ node = append_to(node) { block.call }
622
+ open_conditions.pop
623
+ else
624
+ unless options[:then] || options[:else]
625
+ fail "You must supply a :then or :else option when calling #{name} like this!"
626
+ end
627
+ node = n1(name, flag)
628
+ if options[:then]
629
+ node = append_to(node) { options[:then].call }
630
+ end
631
+ if options[:else]
632
+ e = n0(:else)
633
+ e = append_to(e) { options[:else].call }
634
+ node = node.updated(nil, node.children + [e])
635
+ end
636
+ end
637
+ node
638
+ end
639
+ end
640
+ end
641
+
642
+ def apply_conditions(options, node = nil)
643
+ # Applying the current context, means to append to the same node as the last time, this
644
+ # means that the next node will pick up the exact same condition context as the previous one
645
+ if options[:context] == :current
646
+ node = yield
647
+ found = false
648
+ @pipeline = @pipeline.map do |parent|
649
+ p = Processors::AppendTo.new
650
+ n = p.run(parent, node, @last_append.id)
651
+ found ||= p.succeeded?
652
+ n
653
+ end
654
+ unless found
655
+ fail 'The request to apply the current context has failed, this is likely a bug in OrigenTesters::ATP'
656
+ end
657
+ node
658
+ else
659
+ conditions = extract_conditions(options)
660
+ open_conditions << conditions
661
+ node = yield
662
+ open_conditions.pop
663
+
664
+ update_last_append = !condition_node?(node)
665
+
666
+ conditions.each do |key, value|
667
+ if key == :group
668
+ node = n2(key, n1(:name, value.to_s), node)
669
+ else
670
+ node = n2(key, value, node)
671
+ end
672
+ if update_last_append
673
+ @last_append = node
674
+ update_last_append = false
675
+ end
676
+ end
677
+
678
+ append(node)
679
+ node
680
+ end
681
+ end
682
+
683
+ def save_conditions
684
+ @last_conditions = clean_conditions(open_conditions.dup)
685
+ end
686
+
687
+ def last_conditions
688
+ @last_conditions || {}
689
+ end
690
+
691
+ def open_conditions
692
+ @open_conditions ||= []
693
+ end
694
+
695
+ def clean_conditions(conditions)
696
+ result = {}.with_indifferent_access
697
+ conditions.each do |cond|
698
+ if cond.is_a?(Array)
699
+ if cond.size != 2
700
+ fail 'Something has gone wrong in OrigenTesters::ATP!'
701
+ else
702
+ result[cond[0]] = cond[1].to_s if cond[1]
703
+ end
704
+ else
705
+ cond.each { |k, v| result[k] = v.to_s if v }
706
+ end
707
+ end
708
+ result
709
+ end
710
+
711
+ def extract_conditions(options)
712
+ conditions = {}
713
+ delete_from_options = !options.delete(:_dont_delete_conditions_)
714
+ options.each do |key, value|
715
+ if CONDITION_KEYS[key]
716
+ options.delete(key) if delete_from_options
717
+ key = CONDITION_KEYS[key]
718
+ if conditions[key] && value
719
+ fail "Multiple values assigned to flow condition #{key}" unless conditions[key] == value
720
+ else
721
+ conditions[key] = value if value
722
+ end
723
+ end
724
+ end
725
+ conditions
726
+ end
727
+
728
+ def append(node)
729
+ @last_append = @pipeline.last unless condition_node?(node)
730
+ n = @pipeline.pop
731
+ @pipeline << n.updated(nil, n.children + [node])
732
+ @pipeline.last
733
+ end
734
+
735
+ # Append all nodes generated within the given block to the given node
736
+ # instead of the top-level flow node
737
+ def append_to(node)
738
+ @pipeline << node
739
+ yield
740
+ @pipeline.pop
741
+ end
742
+
743
+ def condition_node?(node)
744
+ !!CONDITION_KEYS[node.type]
745
+ end
746
+
747
+ def extract_meta!(options)
748
+ @source_file << options.delete(:source_file)
749
+ @source_line_number << options.delete(:source_line_number)
750
+ @description << options.delete(:description)
751
+ yield
752
+ @source_file.pop
753
+ @source_line_number.pop
754
+ @description.pop
755
+ end
756
+
757
+ def id(name)
758
+ n1(:id, name)
759
+ end
760
+
761
+ def on_fail(options = {})
762
+ if options.is_a?(Proc)
763
+ node = n0(:on_fail)
764
+ append_to(node) { options.call }
765
+ else
766
+ children = []
767
+ if options[:bin] || options[:softbin]
768
+ fail_opts = { bin: options[:bin], softbin: options[:softbin] }
769
+ fail_opts[:bin_description] = options[:bin_description] if options[:bin_description]
770
+ fail_opts[:softbin_description] = options[:softbin_description] if options[:softbin_description]
771
+ fail_opts[:bin_attrs] = options[:bin_attrs] if options[:bin_attrs]
772
+ children << set_result(:fail, fail_opts)
773
+ end
774
+ if options[:set_run_flag] || options[:set_flag]
775
+ children << set_flag_node(options[:set_run_flag] || options[:set_flag])
776
+ end
777
+ children << n0(:continue) if options[:continue]
778
+ children << n1(:delayed, !!options[:delayed]) if options.key?(:delayed)
779
+ children << n1(:render, options[:render]) if options[:render]
780
+ n(:on_fail, children)
781
+ end
782
+ end
783
+
784
+ def on_pass(options = {})
785
+ if options.is_a?(Proc)
786
+ node = n0(:on_pass)
787
+ append_to(node) { options.call }
788
+ else
789
+ children = []
790
+ if options[:bin] || options[:softbin]
791
+ pass_opts = { bin: options[:bin], softbin: options[:softbin] }
792
+ pass_opts[:bin_description] = options[:bin_description] if options[:bin_description]
793
+ pass_opts[:softbin_description] = options[:softbin_description] if options[:softbin_description]
794
+ pass_opts[:bin_attrs] = options[:bin_attrs] if options[:bin_attrs]
795
+ children << set_result(:pass, pass_opts)
796
+ end
797
+ if options[:set_run_flag] || options[:set_flag]
798
+ children << set_flag_node(options[:set_run_flag] || options[:set_flag])
799
+ end
800
+ children << n0(:continue) if options[:continue]
801
+ children << n1(:render, options[:render]) if options[:render]
802
+ n(:on_pass, children)
803
+ end
804
+ end
805
+
806
+ def pattern(name, path = nil)
807
+ if path
808
+ n2(:pattern, name, path)
809
+ else
810
+ n1(:pattern, name)
811
+ end
812
+ end
813
+
814
+ def attribute(name, value)
815
+ n2(:attribute, name, value)
816
+ end
817
+
818
+ def level(name, value, units = nil)
819
+ if units
820
+ n(:level, [name, value, units])
821
+ else
822
+ n2(:level, name, value)
823
+ end
824
+ end
825
+
826
+ def pin(name)
827
+ n1(:pin, name)
828
+ end
829
+
830
+ def set_result(type, options = {})
831
+ children = []
832
+ children << type
833
+ if options[:bin] && options[:bin_description]
834
+ children << n2(:bin, options[:bin], options[:bin_description])
835
+ else
836
+ children << n1(:bin, options[:bin]) if options[:bin]
837
+ end
838
+ if options[:softbin] && options[:softbin_description]
839
+ children << n2(:softbin, options[:softbin], options[:softbin_description])
840
+ else
841
+ children << n1(:softbin, options[:softbin]) if options[:softbin]
842
+ end
843
+ if options[:bin_attrs]
844
+ options[:bin_attrs].each do |key, val|
845
+ children << n1(key, val)
846
+ end
847
+ end
848
+ n(:set_result, children)
849
+ end
850
+
851
+ def number(val)
852
+ n1(:number, val.to_i)
853
+ end
854
+
855
+ def set_flag_node(flag)
856
+ n1(:set_flag, flag)
857
+ end
858
+
859
+ # Ensures the flow ast has a volatile node, then adds the
860
+ # given flags to it
861
+ def add_volatile_flags(node, flags)
862
+ name, *nodes = *node
863
+ if nodes[0] && nodes[0].type == :volatile
864
+ v = nodes.shift
865
+ else
866
+ v = n0(:volatile)
867
+ end
868
+ existing = v.children.map { |f| f.type == :flag ? f.value : nil }.compact
869
+ new = []
870
+ flags.each do |flag|
871
+ new << n1(:flag, flag) unless existing.include?(flag)
872
+ end
873
+ v = v.updated(nil, v.children + new)
874
+ node.updated(nil, [name, v] + nodes)
875
+ end
876
+
877
+ # Ensures the flow ast has a bin descriptions node, then adds the
878
+ # given description to it
879
+ def add_bin_description(node, number, description, options)
880
+ @existing_bin_descriptions ||= { soft: {}, hard: {} }
881
+ return node if @existing_bin_descriptions[options[:type]][number]
882
+ @existing_bin_descriptions[options[:type]][number] = true
883
+ name, *nodes = *node
884
+ if nodes[0] && nodes[0].type == :volatile
885
+ v = nodes.shift
886
+ else
887
+ v = nil
888
+ end
889
+ if nodes[0] && nodes[0].type == :bin_descriptions
890
+ d = nodes.shift
891
+ else
892
+ d = n0(:bin_descriptions)
893
+ end
894
+ d = d.updated(nil, d.children + [n2(options[:type], number, description)])
895
+ node.updated(nil, [name, v, d].compact + nodes)
896
+ end
897
+
898
+ def n(type, children, options = {})
899
+ options[:file] ||= options.delete(:source_file) || source_file
900
+ options[:line_number] ||= options.delete(:source_line_number) || source_line_number
901
+ options[:description] ||= options.delete(:description) || description
902
+ # Guarantee that each node has a unique meta-ID, in case we need to ever search
903
+ # for it
904
+ options[:id] = OrigenTesters::ATP.next_id
905
+ OrigenTesters::ATP::AST::Node.new(type, children, options)
906
+ end
907
+
908
+ def n0(type, options = {})
909
+ n(type, [], options)
910
+ end
911
+
912
+ def n1(type, arg, options = {})
913
+ n(type, [arg], options)
914
+ end
915
+
916
+ def n2(type, arg1, arg2, options = {})
917
+ n(type, [arg1, arg2], options)
918
+ end
919
+ end
920
+ end