atp 0.8.0 → 1.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f75bbab3b302da47d1dee34b1d3ee5ff9e19fbf8
4
- data.tar.gz: bdd796b0790974ab59638e3eb1c3a6d0978838e4
3
+ metadata.gz: 898bb2e160b6d893026e211eba9cf858387c33b5
4
+ data.tar.gz: f6e78515801ad71eed4358f3c5373df08a32e115
5
5
  SHA512:
6
- metadata.gz: c75c86487c1fa3db9cd8ec84b754850b4cb308c6cd5bd6c2fbc96342bf300d433154e66787b66bd896b484677f6c367102b641f9bdd5166f2b8be1a62a054767
7
- data.tar.gz: eb3547a04a6b3ceea52ac58c831d5d320a1c818ffd161195207f858b0b30b592853ab94edeae371684221def67a0ee7075ee8c8ca7bb6e6af7890469b58e59b5
6
+ metadata.gz: ef51a2e7bdcd532882e9266db0f5be8537e9079dd6f6c370b4ccff34e989737539f7fa7123cdbadf7d388540f29570946a49bde56d4b3f63b5bb740753acf92e
7
+ data.tar.gz: c35f0f652e10edf1634b8ed839c4d77efbbfed048b80529d73c61ff08b7987ce35355e10f73ab4e9fa5b18abca593ad4ee89b1fcbca715019e0a648e743969c9
@@ -20,13 +20,10 @@ aliases ={
20
20
  # Now branch to the specific task code
21
21
  case @command
22
22
 
23
- # Here is an example of how to implement a command, the logic can go straight
24
- # in here or you can require an external file if preferred.
25
- when "my_command"
26
- puts "Doing something..."
27
- require "commands/my_command" # Would load file lib/commands/my_command.rb
28
- # You must always exit upon successfully capturing a command to prevent
29
- # control flowing back to Origen
23
+ when "tags"
24
+ Dir.chdir Origen.root do
25
+ system "ripper-tags --recursive lib"
26
+ end
30
27
  exit 0
31
28
 
32
29
  # Example of how to make a command to run unit tests, this simply invokes RSpec on
@@ -70,6 +67,7 @@ else
70
67
  # before handing control back to Origen. Un-comment the example below to get started.
71
68
  @application_commands = <<-EOT
72
69
  specs Run the specs (tests), -c will enable coverage
70
+ tags Generate ctags for this app
73
71
  EOT
74
72
  # examples Run the examples (tests), -c will enable coverage
75
73
 
@@ -1,6 +1,6 @@
1
1
  module ATP
2
- MAJOR = 0
3
- MINOR = 8
2
+ MAJOR = 1
3
+ MINOR = 0
4
4
  BUGFIX = 0
5
5
  DEV = nil
6
6
 
data/lib/atp.rb CHANGED
@@ -9,12 +9,39 @@ module ATP
9
9
  autoload :Runner, 'atp/runner'
10
10
  autoload :Formatter, 'atp/formatter'
11
11
  autoload :Parser, 'atp/parser'
12
+ autoload :FlowAPI, 'atp/flow_api'
12
13
 
13
14
  module AST
14
15
  autoload :Node, 'atp/ast/node'
15
- autoload :Builder, 'atp/ast/builder'
16
- autoload :Factories, 'atp/ast/factories'
17
16
  autoload :Extractor, 'atp/ast/extractor'
17
+
18
+ # This is a shim to help backwards compatibility with ATP v0
19
+ module Builder
20
+ class LazyObject < ::BasicObject
21
+ def initialize(&callable)
22
+ @callable = callable
23
+ end
24
+
25
+ def __target_object__
26
+ @__target_object__ ||= @callable.call
27
+ end
28
+
29
+ def method_missing(method_name, *args, &block)
30
+ __target_object__.send(method_name, *args, &block)
31
+ end
32
+ end
33
+
34
+ # Some trickery to lazy load this to fire a deprecation warning if an app references it
35
+ CONDITION_KEYS ||= LazyObject.new do
36
+ 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'
37
+ [:if_enabled, :enabled, :enable_flag, :enable, :if_enable, :unless_enabled, :not_enabled,
38
+ :disabled, :disable, :unless_enable, :if_failed, :unless_passed, :failed, :if_passed,
39
+ :unless_failed, :passed, :if_ran, :if_executed, :unless_ran, :unless_executed, :job,
40
+ :jobs, :if_job, :if_jobs, :unless_job, :unless_jobs, :if_any_failed, :unless_all_passed,
41
+ :if_all_failed, :unless_any_passed, :if_any_passed, :unless_all_failed, :if_all_passed,
42
+ :unless_any_failed, :if_flag, :unless_flag]
43
+ end
44
+ end
18
45
  end
19
46
 
20
47
  # Processors actually modify the AST to clean and optimize the user input
@@ -23,11 +50,22 @@ module ATP
23
50
  autoload :Condition, 'atp/processors/condition'
24
51
  autoload :Relationship, 'atp/processors/relationship'
25
52
  autoload :PreCleaner, 'atp/processors/pre_cleaner'
26
- autoload :PostCleaner, 'atp/processors/post_cleaner'
27
53
  autoload :Marshal, 'atp/processors/marshal'
28
54
  autoload :AddIDs, 'atp/processors/add_ids'
29
55
  autoload :AddSetResult, 'atp/processors/add_set_result'
30
56
  autoload :FlowID, 'atp/processors/flow_id'
57
+ autoload :EmptyBranchRemover, 'atp/processors/empty_branch_remover'
58
+ autoload :AppendTo, 'atp/processors/append_to'
59
+ autoload :Flattener, 'atp/processors/flattener'
60
+ autoload :RedundantConditionRemover, 'atp/processors/redundant_condition_remover'
61
+ autoload :ElseRemover, 'atp/processors/else_remover'
62
+ autoload :OnPassFailRemover, 'atp/processors/on_pass_fail_remover'
63
+ autoload :ApplyPostGroupActions, 'atp/processors/apply_post_group_actions'
64
+ autoload :OneFlagPerTest, 'atp/processors/one_flag_per_test'
65
+ autoload :FlagOptimizer, 'atp/processors/flag_optimizer'
66
+ autoload :AdjacentIfCombiner, 'atp/processors/adjacent_if_combiner'
67
+ autoload :ContinueImplementer, 'atp/processors/continue_implementer'
68
+ autoload :ExtractSetFlags, 'atp/processors/extract_set_flags'
31
69
  end
32
70
 
33
71
  # Summarizers extract summary data from the given AST
@@ -48,18 +86,11 @@ module ATP
48
86
  module Formatters
49
87
  autoload :Basic, 'atp/formatters/basic'
50
88
  autoload :Datalog, 'atp/formatters/datalog'
51
- autoload :Graph, 'atp/formatters/graph'
52
- end
53
-
54
- def self.or(*args)
55
- OR.new(*args)
56
- end
57
-
58
- def self.and(*args)
59
- AND.new(*args)
60
89
  end
61
90
 
62
- def self.not(*args)
63
- NOT.new(*args)
91
+ # Maintains a unique ID counter to ensure that all nodes get a unique ID
92
+ def self.next_id
93
+ @next_id ||= 0
94
+ @next_id += 1
64
95
  end
65
96
  end
@@ -2,9 +2,8 @@ require 'ast'
2
2
  module ATP
3
3
  module AST
4
4
  class Node < ::AST::Node
5
- include Factories
6
-
7
5
  attr_reader :file, :line_number, :description
6
+ attr_accessor :id
8
7
 
9
8
  def initialize(type, children = [], properties = {})
10
9
  # Always use strings instead of symbols in the AST, makes serializing
@@ -21,6 +20,11 @@ module ATP
21
20
  end
22
21
  end
23
22
 
23
+ # Returns true if the node carries source file data, retrieve it via the source method
24
+ def has_source?
25
+ !!file
26
+ end
27
+
24
28
  # Create a new node from the given S-expression (a string)
25
29
  def self.from_sexp(sexp)
26
30
  @parser ||= Parser.new
@@ -29,14 +33,14 @@ module ATP
29
33
 
30
34
  # Adds an empty node of the given type to the children unless another
31
35
  # node of the same type is already present
32
- def ensure_node_present(type, child_nodes = nil)
36
+ def ensure_node_present(type, *child_nodes)
33
37
  if children.any? { |n| n.type == type }
34
38
  self
35
39
  else
36
- if child_nodes
37
- node = n(type, *child_nodes)
40
+ if !child_nodes.empty?
41
+ node = updated(type, child_nodes)
38
42
  else
39
- node = n0(type)
43
+ node = updated(type, [])
40
44
  end
41
45
  updated(nil, children + [node])
42
46
  end
@@ -68,15 +72,20 @@ module ATP
68
72
  updated(nil, children - nodes)
69
73
  end
70
74
 
71
- # Returns the first child node of the given type that is found
72
- def find(type)
73
- children.find { |c| c.try(:type) == type }
75
+ # Returns the first child node of the given type(s) that is found
76
+ def find(*types)
77
+ children.find { |c| types.include?(c.try(:type)) }
74
78
  end
75
79
 
76
80
  # Returns an array containing all child nodes of the given type(s)
77
81
  def find_all(*types)
78
82
  children.select { |c| types.include?(c.try(:type)) }
79
83
  end
84
+
85
+ # Returns an array containing all flags which are set within the given node
86
+ def set_flags
87
+ Processors::ExtractSetFlags.new.run(self)
88
+ end
80
89
  end
81
90
  end
82
91
  end
@@ -3,41 +3,179 @@ module ATP
3
3
  # with an abstract test program
4
4
  class Flow
5
5
  attr_reader :program, :name
6
- # Returns the raw AST
7
- attr_reader :raw
8
- attr_accessor :id
6
+
7
+ attr_accessor :source_file, :source_line_number, :description
8
+
9
+ CONDITION_KEYS = {
10
+ if_enabled: :if_enabled,
11
+ if_enable: :if_enabled,
12
+ enabled: :if_enabled,
13
+ enable_flag: :if_enabled,
14
+ enable: :if_enabled,
15
+
16
+ unless_enabled: :unless_enabled,
17
+ not_enabled: :unless_enabled,
18
+ disabled: :unless_enabled,
19
+ disable: :unless_enabled,
20
+ unless_enable: :unless_enabled,
21
+
22
+ if_failed: :if_failed,
23
+ unless_passed: :if_failed,
24
+ failed: :if_failed,
25
+
26
+ if_passed: :if_passed,
27
+ unless_failed: :if_passed,
28
+ passed: :if_passed,
29
+
30
+ if_any_failed: :if_any_failed,
31
+ unless_all_passed: :if_any_failed,
32
+
33
+ if_all_failed: :if_all_failed,
34
+ unless_any_passed: :if_all_failed,
35
+
36
+ if_any_passed: :if_any_passed,
37
+ unless_all_failed: :if_any_passed,
38
+
39
+ if_all_passed: :if_all_passed,
40
+ unless_any_failed: :if_all_passed,
41
+
42
+ if_ran: :if_ran,
43
+ if_executed: :if_ran,
44
+
45
+ unless_ran: :unless_ran,
46
+ unless_executed: :unless_ran,
47
+
48
+ job: :if_job,
49
+ jobs: :if_job,
50
+ if_job: :if_job,
51
+ if_jobs: :if_job,
52
+
53
+ unless_job: :unless_job,
54
+ unless_jobs: :unless_job,
55
+
56
+ if_flag: :if_flag,
57
+
58
+ unless_flag: :unless_flag,
59
+
60
+ group: :group
61
+ }
62
+
63
+ CONDITION_NODE_TYPES = CONDITION_KEYS.values.uniq
9
64
 
10
65
  def initialize(program, name = nil, options = {})
11
66
  name, options = nil, name if name.is_a?(Hash)
12
67
  extract_meta!(options)
13
68
  @program = program
14
69
  @name = name
15
- @raw = builder.flow(name)
70
+ @pipeline = [n1(:flow, n1(:name, name))]
16
71
  end
17
72
 
18
73
  # @api private
19
74
  def marshal_dump
20
- [@name, @program, Processors::Marshal.new.process(@raw)]
75
+ [@name, @program, Processors::Marshal.new.process(raw)]
21
76
  end
22
77
 
23
78
  # @api private
24
79
  def marshal_load(array)
25
- @name, @program, @raw = array
80
+ @name, @program, raw = array
81
+ @pipeline = [raw]
82
+ end
83
+
84
+ # Returns the raw AST
85
+ def raw
86
+ n = nil
87
+ @pipeline.reverse_each do |node|
88
+ if n
89
+ n = node.updated(nil, node.children + [n])
90
+ else
91
+ n = node
92
+ end
93
+ end
94
+ n
26
95
  end
27
96
 
28
97
  # Returns a processed/optimized AST, this is the one that should be
29
98
  # used to build and represent the given test flow
30
- def ast
31
- ast = Processors::PreCleaner.new.process(raw)
32
- # File.open("a1.txt", "w") { |f| f.write(ast) }
33
- ast = Processors::FlowID.new.run(ast, id) if id
34
- # File.open("a2.txt", "w") { |f| f.write(ast) }
35
- Validators::DuplicateIDs.new(self).process(ast)
36
- Validators::MissingIDs.new(self).process(ast)
37
- ast = Processors::Condition.new.process(ast)
38
- ast = Processors::Relationship.new.process(ast)
39
- ast = Processors::PostCleaner.new.process(ast)
40
- Validators::Jobs.new(self).process(ast)
99
+ def ast(options = {})
100
+ options = {
101
+ apply_relationships: true,
102
+ # Supply a unique ID to append to all IDs
103
+ unique_id: nil,
104
+ # Set to :smt, or :igxl
105
+ optimization: :runner,
106
+ # These options are not intended for application use, but provide the ability to
107
+ # turn off certain processors during test cases
108
+ add_ids: true,
109
+ optimize_flags: true,
110
+ one_flag_per_test: true,
111
+ implement_continue: true
112
+ }.merge(options)
113
+ ###############################################################################
114
+ ## Common pre-processing and validation
115
+ ###############################################################################
116
+ ast = Processors::PreCleaner.new.run(raw)
117
+ Validators::DuplicateIDs.new(self).run(ast)
118
+ Validators::MissingIDs.new(self).run(ast)
119
+ Validators::Jobs.new(self).run(ast)
120
+ # Ensure everything has an ID, this helps later if condition nodes need to be generated
121
+ ast = Processors::AddIDs.new.run(ast) if options[:add_ids]
122
+ ast = Processors::FlowID.new.run(ast, options[:unique_id]) if options[:unique_id]
123
+
124
+ ###############################################################################
125
+ ## Optimization for a C-like flow target, e.g. V93K
126
+ ###############################################################################
127
+ if options[:optimization] == :smt || options[:optimization] == :runner
128
+ # This applies all the relationships by setting flags in the referenced test and
129
+ # changing all if_passed/failed type nodes to if_flag type nodes
130
+ ast = Processors::Relationship.new.run(ast) if options[:apply_relationships]
131
+ ast = Processors::Condition.new.run(ast)
132
+ unless options[:optimization] == :runner
133
+ ast = Processors::ContinueImplementer.new.run(ast) if options[:implement_continue]
134
+ end
135
+ ast = Processors::FlagOptimizer.new.run(ast) if options[:optimize_flags]
136
+ ast = Processors::AdjacentIfCombiner.new.run(ast)
137
+
138
+ ###############################################################################
139
+ ## Optimization for a row-based target, e.g. UltraFLEX
140
+ ###############################################################################
141
+ elsif options[:optimization] == :igxl
142
+ # Un-nest everything embedded in else nodes
143
+ ast = Processors::ElseRemover.new.run(ast)
144
+ # Un-nest everything embedded in on_pass/fail nodes except for binning and
145
+ # flag setting
146
+ ast = Processors::OnPassFailRemover.new.run(ast)
147
+ # This applies all the relationships by setting flags in the referenced test and
148
+ # changing all if_passed/failed type nodes to if_flag type nodes
149
+ ast = Processors::Relationship.new.run(ast) if options[:apply_relationships]
150
+ ast = Processors::Condition.new.run(ast)
151
+ ast = Processors::ApplyPostGroupActions.new.run(ast)
152
+ ast = Processors::OneFlagPerTest.new.run(ast) if options[:one_flag_per_test]
153
+ ast = Processors::RedundantConditionRemover.new.run(ast)
154
+
155
+ ###############################################################################
156
+ ## Not currently used, more of a test case
157
+ ###############################################################################
158
+ elsif options[:optimization] == :flat
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
+ ast = Processors::Condition.new.run(ast)
165
+ ast = Processors::Flattener.new.run(ast)
166
+
167
+ ###############################################################################
168
+ ## Default Optimization
169
+ ###############################################################################
170
+ else
171
+ ast = Processors::Condition.new.run(ast)
172
+ end
173
+
174
+ ###############################################################################
175
+ ## Common cleanup
176
+ ###############################################################################
177
+ # Removes any empty on_pass and on_fail branches
178
+ ast = Processors::EmptyBranchRemover.new.run(ast)
41
179
  ast
42
180
  end
43
181
 
@@ -46,7 +184,7 @@ module ATP
46
184
  def volatile(*flags)
47
185
  options = flags.pop if flags.last.is_a?(Hash)
48
186
  flags = flags.flatten
49
- @raw = builder.add_volatile_flags(@raw, flags)
187
+ @pipeline[0] = add_volatile_flags(@pipeline[0], flags)
50
188
  end
51
189
 
52
190
  # Group all tests generated within the given block
@@ -57,10 +195,15 @@ module ATP
57
195
  # flow.test ...
58
196
  # end
59
197
  def group(name, options = {})
60
- open_groups.push([])
61
- yield
62
198
  extract_meta!(options)
63
- append builder.group(name, open_groups.pop, options)
199
+ apply_conditions(options) do
200
+ children = [n1(:name, name)]
201
+ children << id(options[:id]) if options[:id]
202
+ children << on_fail(options[:on_fail]) if options[:on_fail]
203
+ children << on_pass(options[:on_pass]) if options[:on_pass]
204
+ g = n(:group, children)
205
+ append_to(g) { yield }
206
+ end
64
207
  end
65
208
 
66
209
  # Add a test line to the flow
@@ -74,8 +217,7 @@ module ATP
74
217
  # @option options [Hash] :conditions What conditions must be met to execute the test
75
218
  def test(instance, options = {})
76
219
  extract_meta!(options)
77
- r = options.delete(:return)
78
- t = apply_open_conditions(options) do |options|
220
+ apply_conditions(options) do
79
221
  # Allows any continue, bin, or soft bin argument passed in at the options top-level to be assumed
80
222
  # to be the action to take if the test fails
81
223
  if b = options.delete(:bin)
@@ -100,93 +242,176 @@ module ATP
100
242
  end
101
243
  if f = options.delete(:flag_pass)
102
244
  options[:on_pass] ||= {}
103
- options[:on_pass][:set_run_flag] = f
245
+ options[:on_pass][:set_flag] = f
104
246
  end
105
247
  if f = options.delete(:flag_fail)
106
248
  options[:on_fail] ||= {}
107
- options[:on_fail][:set_run_flag] = f
249
+ options[:on_fail][:set_flag] = f
250
+ end
251
+
252
+ children = [n1(:object, instance)]
253
+
254
+ name = (options[:name] || options[:tname] || options[:test_name])
255
+ unless name
256
+ [:name, :tname, :test_name].each do |m|
257
+ name ||= instance.respond_to?(m) ? instance.send(m) : nil
258
+ end
259
+ end
260
+ children << n1(:name, name) if name
261
+
262
+ num = (options[:number] || options[:num] || options[:tnum] || options[:test_number])
263
+ unless num
264
+ [:number, :num, :tnum, :test_number].each do |m|
265
+ num ||= instance.respond_to?(m) ? instance.send(m) : nil
266
+ end
267
+ end
268
+ children << number(num) if num
269
+
270
+ children << id(options[:id]) if options[:id]
271
+
272
+ if levels = options[:level] || options[:levels]
273
+ levels = [levels] unless levels.is_a?(Array)
274
+ levels.each do |l|
275
+ children << level(l[:name], l[:value], l[:unit] || l[:units])
276
+ end
277
+ end
278
+
279
+ if lims = options[:limit] || options[:limits]
280
+ lims = [lims] unless lims.is_a?(Array)
281
+ lims.each do |l|
282
+ if l.is_a?(Hash)
283
+ children << limit(l[:value], l[:rule], l[:unit] || l[:units])
284
+ end
285
+ end
286
+ end
287
+
288
+ if pins = options[:pin] || options[:pins]
289
+ pins = [pins] unless pins.is_a?(Array)
290
+ pins.each do |p|
291
+ if p.is_a?(Hash)
292
+ children << pin(p[:name])
293
+ else
294
+ children << pin(p)
295
+ end
296
+ end
297
+ end
298
+
299
+ if pats = options[:pattern] || options[:patterns]
300
+ pats = [pats] unless pats.is_a?(Array)
301
+ pats.each do |p|
302
+ if p.is_a?(Hash)
303
+ children << pattern(p[:name], p[:path])
304
+ else
305
+ children << pattern(p)
306
+ end
307
+ end
108
308
  end
109
- builder.test(instance, options)
309
+
310
+ if options[:meta]
311
+ attrs = []
312
+ options[:meta].each { |k, v| attrs << attribute(k, v) }
313
+ children << n(:meta, attrs)
314
+ end
315
+
316
+ if subs = options[:sub_test] || options[:sub_tests]
317
+ subs = [subs] unless subs.is_a?(Array)
318
+ subs.each do |s|
319
+ children << s.updated(:sub_test, nil)
320
+ end
321
+ end
322
+
323
+ children << on_fail(options[:on_fail]) if options[:on_fail]
324
+ children << on_pass(options[:on_pass]) if options[:on_pass]
325
+
326
+ save_conditions
327
+ n(:test, children)
110
328
  end
111
- append(t) unless r
112
- t
113
329
  end
114
330
 
115
331
  # Equivalent to calling test, but returns a sub_test node instead of adding it to the flow.
116
- # It will also ignore any condition nodes that would normally wrap the equivalent flow.test call.
117
332
  #
118
333
  # This is a helper to create sub_tests for inclusion in a top-level test node.
119
334
  def sub_test(instance, options = {})
120
- options[:return] = true
121
- options[:ignore_all_conditions] = true
122
- test(instance, options)
335
+ temp = append_to(n0(:temp)) { test(instance, options) }
336
+ temp.children.first.updated(:sub_test, nil)
123
337
  end
124
338
 
125
339
  def bin(number, options = {})
340
+ if number.is_a?(Hash)
341
+ fail 'The bin number must be passed as the first argument'
342
+ end
343
+ options[:bin_description] ||= options.delete(:description)
126
344
  extract_meta!(options)
127
- t = apply_open_conditions(options) do |options|
128
- fail 'A :type option set to :pass or :fail is required when calling bin' unless options[:type]
345
+ apply_conditions(options) do
346
+ options[:type] ||= :fail
129
347
  options[:bin] = number
130
348
  options[:softbin] ||= options[:soft_bin] || options[:sbin]
131
- builder.set_result(options[:type], options)
349
+ set_result(options[:type], options)
132
350
  end
133
- append(t)
351
+ end
352
+
353
+ def pass(number, options = {})
354
+ if number.is_a?(Hash)
355
+ fail 'The bin number must be passed as the first argument'
356
+ end
357
+ options[:type] = :pass
358
+ bin(number, options)
134
359
  end
135
360
 
136
361
  def cz(instance, cz_setup, options = {})
137
362
  extract_meta!(options)
138
- t = apply_open_conditions(options) do |options|
139
- conditions = options.delete(:conditions)
140
- options[:return] = true
141
- builder.cz(cz_setup, test(instance, options.merge(dont_apply_conditions: true)), conditions: conditions)
363
+ apply_conditions(options) do
364
+ node = n1(:cz, cz_setup)
365
+ append_to(node) { test(instance, options) }
142
366
  end
143
- append(t)
144
367
  end
145
368
  alias_method :characterize, :cz
146
369
 
147
370
  # Append a log message line to the flow
148
371
  def log(message, options = {})
149
372
  extract_meta!(options)
150
- t = apply_open_conditions(options) do |options|
151
- builder.log(message, options)
373
+ apply_conditions(options) do
374
+ n1(:log, message.to_s)
152
375
  end
153
- append(t)
154
376
  end
155
377
 
156
378
  # Enable a flow control variable
157
379
  def enable(var, options = {})
158
380
  extract_meta!(options)
159
- t = apply_open_conditions(options) do |options|
160
- builder.enable_flow_flag(var, options)
381
+ apply_conditions(options) do
382
+ n1(:enable, var)
161
383
  end
162
- append(t)
163
384
  end
164
385
 
165
386
  # Disable a flow control variable
166
387
  def disable(var, options = {})
167
388
  extract_meta!(options)
168
- t = apply_open_conditions(options) do |options|
169
- builder.disable_flow_flag(var, options)
389
+ apply_conditions(options) do
390
+ n1(:disable, var)
391
+ end
392
+ end
393
+
394
+ def set_flag(flag, options = {})
395
+ extract_meta!(options)
396
+ apply_conditions(options) do
397
+ set_flag_node(flag)
170
398
  end
171
- append(t)
172
399
  end
173
400
 
174
401
  # Insert explicitly rendered content in to the flow
175
402
  def render(str, options = {})
176
403
  extract_meta!(options)
177
- t = apply_open_conditions(options) do |options|
178
- builder.render(str, options)
404
+ apply_conditions(options) do
405
+ n1(:render, str)
179
406
  end
180
- append(t)
181
407
  end
182
408
 
183
- def with_condition(options)
409
+ def continue(options = {})
184
410
  extract_meta!(options)
185
- open_conditions.push(options)
186
- yield
187
- open_conditions.pop
411
+ apply_conditions(options) do
412
+ n0(:continue)
413
+ end
188
414
  end
189
- alias_method :with_conditions, :with_condition
190
415
 
191
416
  # Execute the given flow in the console
192
417
  def run(options = {})
@@ -197,139 +422,321 @@ module ATP
197
422
  # Returns true if the test context generated from the supplied options + existing condition
198
423
  # wrappers, is different from that which was applied to the previous test.
199
424
  def context_changed?(options)
200
- a = context
201
- b = build_context(options)
202
- !context_equal?(a, b)
425
+ options[:_dont_delete_conditions_] = true
426
+ last_conditions != clean_conditions(open_conditions + [extract_conditions(options)])
203
427
  end
204
428
 
205
- def context
206
- builder.context
429
+ # Define handlers for all of the flow control block methods, unless a custom one has already
430
+ # been defined above
431
+ CONDITION_KEYS.keys.each do |method|
432
+ define_method method do |*flags, &block|
433
+ if flags.last.is_a?(Hash)
434
+ options = flags.pop
435
+ else
436
+ options = {}
437
+ end
438
+ flags = flags.first if flags.size == 1
439
+ # Legacy option provided by OrigenTesters that permits override of a block enable method by passing
440
+ # an :or option with a true value
441
+ if (CONDITION_KEYS[method] == :if_enabled || CONDITION_KEYS[method] || :unless_enabled) && options[:or]
442
+ block.call
443
+ else
444
+ flow_control_method(CONDITION_KEYS[method], flags, options, &block)
445
+ end
446
+ end unless method_defined?(method)
207
447
  end
208
448
 
209
- def context_equal?(a, b)
210
- if a.size == b.size
211
- a = clean_condition(a[:conditions])
212
- b = clean_condition(b[:conditions])
213
- if a.keys.sort == b.keys.sort
214
- a.all? do |key, value|
215
- value.flatten.uniq.sort == b[key].flatten.uniq.sort
216
- end
217
- end
218
- end
449
+ def inspect
450
+ "<ATP::Flow:#{object_id} #{name}>"
219
451
  end
220
452
 
221
453
  private
222
454
 
223
- def clean_condition(h)
224
- c = {}
225
- h.each do |hash|
226
- key, value = hash.first[0], hash.first[1]
227
- key = clean_key(key)
228
- value = clean_value(value)
229
- c[key] ||= []
230
- c[key] << value unless c[key].include?(value)
455
+ def flow_control_method(name, flag, options = {}, &block)
456
+ extract_meta!(options)
457
+ if flag.is_a?(Array)
458
+ if name == :if_passed
459
+ fail 'if_passed only accepts one ID, use if_any_passed or if_all_passed for multiple IDs'
460
+ end
461
+ if name == :if_failed
462
+ fail 'if_failed only accepts one ID, use if_any_failed or if_all_failed for multiple IDs'
463
+ end
464
+ end
465
+ apply_conditions(options) do
466
+ if block
467
+ node = n1(name, flag)
468
+ open_conditions << [name, flag]
469
+ node = append_to(node) { block.call }
470
+ open_conditions.pop
471
+ else
472
+ unless options[:then] || options[:else]
473
+ fail "You must supply a :then or :else option when calling #{name} like this!"
474
+ end
475
+ node = n1(name, flag)
476
+ if options[:then]
477
+ node = append_to(node) { options[:then].call }
478
+ end
479
+ if options[:else]
480
+ e = n0(:else)
481
+ e = append_to(e) { options[:else].call }
482
+ node = node.updated(nil, node.children + [e])
483
+ end
484
+ end
485
+ node
231
486
  end
232
- c
233
487
  end
234
488
 
235
- def clean_value(value)
236
- if value.is_a?(Array)
237
- value.map { |v| v.to_s.downcase }.sort
489
+ def apply_conditions(options, node = nil)
490
+ # Applying the current context, means to append to the same node as the last time, this
491
+ # means that the next node will pick up the exact same condition context as the previous one
492
+ if options[:context] == :current
493
+ node = yield
494
+ found = false
495
+ @pipeline = @pipeline.map do |parent|
496
+ p = Processors::AppendTo.new
497
+ n = p.run(parent, node, @last_append.id)
498
+ found ||= p.succeeded?
499
+ n
500
+ end
501
+ unless found
502
+ fail 'The request to apply the current context has failed, this is likely a bug in the ATP plugin'
503
+ end
504
+ node
238
505
  else
239
- value.to_s.downcase
506
+ conditions = extract_conditions(options)
507
+ open_conditions << conditions
508
+ node = yield
509
+ open_conditions.pop
510
+
511
+ update_last_append = !condition_node?(node)
512
+
513
+ conditions.each do |key, value|
514
+ if key == :group
515
+ node = n2(key, n1(:name, value.to_s), node)
516
+ else
517
+ node = n2(key, value, node)
518
+ end
519
+ if update_last_append
520
+ @last_append = node
521
+ update_last_append = false
522
+ end
523
+ end
524
+
525
+ append(node)
526
+ node
240
527
  end
241
528
  end
242
529
 
243
- def clean_key(key)
244
- case key.to_sym
245
- when :if_enabled, :enabled, :enable_flag, :enable, :if_enable
246
- :if_enable
247
- when :unless_enabled, :not_enabled, :disabled, :disable, :unless_enable
248
- :unless_enable
249
- when :if_failed, :unless_passed, :failed
250
- :if_failed
251
- when :if_passed, :unless_failed, :passed
252
- :if_passed
253
- when :if_any_failed, :unless_all_passed
254
- :if_any_failed
255
- when :if_all_failed, :unless_any_passed
256
- :if_all_failed
257
- when :if_any_passed, :unless_all_failed
258
- :if_any_passed
259
- when :if_all_passed, :unless_any_failed
260
- :if_all_passed
261
- when :if_ran, :if_executed
262
- :if_ran
263
- when :unless_ran, :unless_executed
264
- :unless_ran
265
- when :job, :jobs, :if_job, :if_jobs
266
- :if_job
267
- when :unless_job, :unless_jobs
268
- :unless_job
269
- else
270
- fail "Unknown test condition attribute - #{key}"
530
+ def save_conditions
531
+ @last_conditions = clean_conditions(open_conditions.dup)
532
+ end
533
+
534
+ def last_conditions
535
+ @last_conditions || {}
536
+ end
537
+
538
+ def open_conditions
539
+ @open_conditions ||= []
540
+ end
541
+
542
+ def clean_conditions(conditions)
543
+ result = {}.with_indifferent_access
544
+ conditions.each do |cond|
545
+ if cond.is_a?(Array)
546
+ if cond.size != 2
547
+ fail 'Something has gone wrong in ATP!'
548
+ else
549
+ result[cond[0]] = cond[1].to_s if cond[1]
550
+ end
551
+ else
552
+ cond.each { |k, v| result[k] = v.to_s if v }
553
+ end
271
554
  end
555
+ result
272
556
  end
273
557
 
274
- def build_context(options)
275
- c = open_conditions.dup
276
- if options[:conditions]
277
- options[:conditions].each do |key, value|
278
- c << { key => value }
558
+ def extract_conditions(options)
559
+ conditions = {}
560
+ delete_from_options = !options.delete(:_dont_delete_conditions_)
561
+ options.each do |key, value|
562
+ if CONDITION_KEYS[key]
563
+ options.delete(key) if delete_from_options
564
+ key = CONDITION_KEYS[key]
565
+ if conditions[key] && value
566
+ fail "Multiple values assigned to flow condition #{key}" unless conditions[key] == value
567
+ else
568
+ conditions[key] = value if value
569
+ end
279
570
  end
280
571
  end
281
- { conditions: c }
572
+ conditions
573
+ end
574
+
575
+ def append(node)
576
+ @last_append = @pipeline.last unless condition_node?(node)
577
+ n = @pipeline.pop
578
+ @pipeline << n.updated(nil, n.children + [node])
579
+ @pipeline.last
580
+ end
581
+
582
+ # Append all nodes generated within the given block to the given node
583
+ # instead of the top-level flow node
584
+ def append_to(node)
585
+ @pipeline << node
586
+ yield
587
+ @pipeline.pop
588
+ end
589
+
590
+ def condition_node?(node)
591
+ !!CONDITION_KEYS[node.type]
592
+ end
593
+
594
+ def extract_meta!(options)
595
+ self.source_file = options.delete(:source_file)
596
+ self.source_line_number = options.delete(:source_line_number)
597
+ self.description = options.delete(:description)
282
598
  end
283
599
 
284
- def builder
285
- @builder ||= AST::Builder.new
600
+ def id(name)
601
+ n1(:id, name)
286
602
  end
287
603
 
288
- def apply_open_conditions(options)
289
- if options[:ignore_all_conditions]
290
- yield(options)
604
+ def on_fail(options = {})
605
+ if options.is_a?(Proc)
606
+ node = n0(:on_fail)
607
+ append_to(node) { options.call }
291
608
  else
292
- if options[:context] == :current
293
- options[:conditions] = builder.context[:conditions]
609
+ children = []
610
+ if options[:bin] || options[:softbin]
611
+ fail_opts = { bin: options[:bin], softbin: options[:softbin] }
612
+ fail_opts[:bin_description] = options[:bin_description] if options[:bin_description]
613
+ fail_opts[:softbin_description] = options[:softbin_description] if options[:softbin_description]
614
+ children << set_result(:fail, fail_opts)
294
615
  end
295
- builder.new_context
296
- t = yield(options)
297
- unless options[:context] == :current
298
- unless options[:dont_apply_conditions]
299
- open_conditions.each do |conditions|
300
- t = builder.apply_conditions(t, conditions)
301
- end
302
- end
616
+ if options[:set_run_flag] || options[:set_flag]
617
+ children << set_flag_node(options[:set_run_flag] || options[:set_flag])
303
618
  end
304
- t
619
+ children << n0(:continue) if options[:continue]
620
+ children << n1(:render, options[:render]) if options[:render]
621
+ n(:on_fail, children)
305
622
  end
306
623
  end
307
624
 
308
- def extract_meta!(options)
309
- builder.source_file = options.delete(:source_file)
310
- builder.source_line_number = options.delete(:source_line_number)
311
- builder.description = options.delete(:description)
625
+ def on_pass(options = {})
626
+ if options.is_a?(Proc)
627
+ node = n0(:on_pass)
628
+ append_to(node) { options.call }
629
+ else
630
+ children = []
631
+ if options[:bin] || options[:softbin]
632
+ pass_opts = { bin: options[:bin], softbin: options[:softbin] }
633
+ pass_opts[:bin_description] = options[:bin_description] if options[:bin_description]
634
+ pass_opts[:softbin_description] = options[:softbin_description] if options[:softbin_description]
635
+ children << set_result(:pass, pass_opts)
636
+ end
637
+ if options[:set_run_flag] || options[:set_flag]
638
+ children << set_flag_node(options[:set_run_flag] || options[:set_flag])
639
+ end
640
+ children << n0(:continue) if options[:continue]
641
+ children << n1(:render, options[:render]) if options[:render]
642
+ n(:on_pass, children)
643
+ end
312
644
  end
313
645
 
314
- # For testing
315
- def raw=(ast)
316
- @raw = ast
646
+ def pattern(name, path = nil)
647
+ if path
648
+ n2(:pattern, name, path)
649
+ else
650
+ n1(:pattern, name)
651
+ end
317
652
  end
318
653
 
319
- def open_conditions
320
- @open_conditions ||= []
654
+ def attribute(name, value)
655
+ n2(:attribute, name, value)
321
656
  end
322
657
 
323
- def open_groups
324
- @open_groups ||= []
658
+ def level(name, value, units = nil)
659
+ if units
660
+ n(:level, [name, value, units])
661
+ else
662
+ n2(:level, name, value)
663
+ end
325
664
  end
326
665
 
327
- def append(node)
328
- if open_groups.empty?
329
- @raw = @raw.updated(nil, @raw.children + [node])
666
+ def limit(value, rule, units = nil)
667
+ if units
668
+ n(:limit, [value, rule, units])
669
+ else
670
+ n2(:limit, value, rule)
671
+ end
672
+ end
673
+
674
+ def pin(name)
675
+ n1(:pin, name)
676
+ end
677
+
678
+ def set_result(type, options = {})
679
+ children = []
680
+ children << type
681
+ if options[:bin] && options[:bin_description]
682
+ children << n2(:bin, options[:bin], options[:bin_description])
330
683
  else
331
- open_groups.last << node
684
+ children << n1(:bin, options[:bin]) if options[:bin]
332
685
  end
686
+ if options[:softbin] && options[:softbin_description]
687
+ children << n2(:softbin, options[:softbin], options[:softbin_description])
688
+ else
689
+ children << n1(:softbin, options[:softbin]) if options[:softbin]
690
+ end
691
+ n(:set_result, children)
692
+ end
693
+
694
+ def number(val)
695
+ n1(:number, val.to_i)
696
+ end
697
+
698
+ def set_flag_node(flag)
699
+ n1(:set_flag, flag)
700
+ end
701
+
702
+ # Ensures the flow ast has a volatile node, then adds the
703
+ # given flags to it
704
+ def add_volatile_flags(node, flags)
705
+ name, *nodes = *node
706
+ if nodes[0] && nodes[0].type == :volatile
707
+ v = nodes.shift
708
+ else
709
+ v = n0(:volatile)
710
+ end
711
+ existing = v.children.map { |f| f.type == :flag ? f.value : nil }.compact
712
+ new = []
713
+ flags.each do |flag|
714
+ new << n1(:flag, flag) unless existing.include?(flag)
715
+ end
716
+ v = v.updated(nil, v.children + new)
717
+ node.updated(nil, [name, v] + nodes)
718
+ end
719
+
720
+ def n(type, children, options = {})
721
+ options[:file] ||= options.delete(:source_file) || source_file
722
+ options[:line_number] ||= options.delete(:source_line_number) || source_line_number
723
+ options[:description] ||= options.delete(:description) || description
724
+ # Guarantee that each node has a unique meta-ID, in case we need to ever search
725
+ # for it
726
+ options[:id] = ATP.next_id
727
+ ATP::AST::Node.new(type, children, options)
728
+ end
729
+
730
+ def n0(type, options = {})
731
+ n(type, [], options)
732
+ end
733
+
734
+ def n1(type, arg, options = {})
735
+ n(type, [arg], options)
736
+ end
737
+
738
+ def n2(type, arg1, arg2, options = {})
739
+ n(type, [arg1, arg2], options)
333
740
  end
334
741
  end
335
742
  end