atp 0.8.0 → 1.0.0

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