origen_testers 0.21.0 → 0.30.0

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