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.
@@ -0,0 +1,18 @@
1
+ module ATP
2
+ module Processors
3
+ # Extracts all flags which are set within the given flow, returning
4
+ # them in an array
5
+ class ExtractSetFlags < ATP::Processor
6
+ def run(nodes)
7
+ @results = []
8
+ process_all(nodes)
9
+ @results.uniq
10
+ end
11
+
12
+ def on_set_flag(node)
13
+ flag = node.value
14
+ @results << flag
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,214 @@
1
+ module ATP
2
+ module Processors
3
+ # This processor eliminates the use of run flags between adjacent tests:
4
+ #
5
+ # s(:flow,
6
+ # s(:name, "prb1"),
7
+ # s(:test,
8
+ # s(:name, "test1"),
9
+ # s(:id, "t1"),
10
+ # s(:on_fail,
11
+ # s(:set_flag, "t1_FAILED", "auto_generated"),
12
+ # s(:continue))),
13
+ # s(:if_flag, "t1_FAILED",
14
+ # s(:test,
15
+ # s(:name, "test2"))))
16
+ #
17
+ #
18
+ # s(:flow,
19
+ # s(:name, "prb1"),
20
+ # s(:test,
21
+ # s(:name, "test1"),
22
+ # s(:id, "t1"),
23
+ # s(:on_fail,
24
+ # s(:test,
25
+ # s(:name, "test2")))))
26
+ #
27
+ class FlagOptimizer < Processor
28
+ attr_reader :run_flag_table
29
+
30
+ class ExtractRunFlagTable < Processor
31
+ # Hash table of run_flag name with number of times used
32
+ attr_reader :run_flag_table
33
+
34
+ # Reset hash table
35
+ def initialize
36
+ @run_flag_table = {}.with_indifferent_access
37
+ end
38
+
39
+ # For run_flag nodes, increment # of occurrences for specified flag
40
+ def on_if_flag(node)
41
+ children = node.children.dup
42
+ names = children.shift
43
+ state = node.type == :if_flag
44
+ Array(names).each do |name|
45
+ if @run_flag_table[name.to_sym].nil?
46
+ @run_flag_table[name.to_sym] = 1
47
+ else
48
+ @run_flag_table[name.to_sym] += 1
49
+ end
50
+ end
51
+ process_all(node.children)
52
+ end
53
+ alias_method :on_unless_flag, :on_if_flag
54
+ end
55
+
56
+ def run(node)
57
+ # Pre-process the AST for # of occurrences of each run-flag used
58
+ t = ExtractRunFlagTable.new
59
+ t.process(node)
60
+ @run_flag_table = t.run_flag_table
61
+ extract_volatiles(node)
62
+ process(node)
63
+ end
64
+
65
+ def on_named_collection(node)
66
+ name, *nodes = *node
67
+ node.updated(nil, [name] + optimize(process_all(nodes)))
68
+ end
69
+ alias_method :on_flow, :on_named_collection
70
+ alias_method :on_group, :on_named_collection
71
+ alias_method :on_unless_flag, :on_named_collection
72
+
73
+ def on_if_flag(node)
74
+ name, *nodes = *node
75
+ # Remove this node and return its children if required
76
+ if if_run_flag_to_remove.last == node.to_a[0]
77
+ node.updated(:inline, node.to_a[1..-1])
78
+ else
79
+ node.updated(nil, [name] + optimize(process_all(nodes)))
80
+ end
81
+ end
82
+
83
+ def on_on_fail(node)
84
+ if to_inline = nodes_to_inline_on_pass_or_fail.last
85
+ # If this node sets the flag that gates the node to be inlined
86
+ set_flag = node.find(:set_flag)
87
+ if set_flag && gated_by_set?(set_flag.to_a[0], to_inline)
88
+ # Remove the sub-node that sets the flag if there are no further references to it
89
+
90
+ if @run_flag_table[set_flag.to_a[0]] == 1 || !@run_flag_table[set_flag.to_a[0]]
91
+ node = node.updated(nil, node.children - [set_flag])
92
+ end
93
+
94
+ # And append the content of the node to be in_lined at the end of this on pass/fail node
95
+ append = reorder_nested_run_flags(set_flag.to_a[0], to_inline).to_a[1..-1]
96
+
97
+ # Belt and braces approach to make sure this node to be inlined does
98
+ # not get picked up anywhere else
99
+ nodes_to_inline_on_pass_or_fail.pop
100
+ nodes_to_inline_on_pass_or_fail << nil
101
+ end
102
+ end
103
+ node.updated(nil, optimize(process_all(node.children + Array(append))))
104
+ end
105
+ alias_method :on_on_pass, :on_on_fail
106
+
107
+ def optimize(nodes)
108
+ results = []
109
+ node1 = nil
110
+ nodes.each do |node2|
111
+ if node1
112
+ if can_be_combined?(node1, node2)
113
+ node1 = combine(node1, node2)
114
+ else
115
+ results << node1
116
+ node1 = node2
117
+ end
118
+ else
119
+ node1 = node2
120
+ end
121
+ end
122
+ results << node1 if node1
123
+ results
124
+ end
125
+
126
+ def can_be_combined?(node1, node2)
127
+ if node1.type == :test && (node2.type == :if_flag || node2.type == :unless_flag)
128
+ if node1.find_all(:on_fail, :on_pass).any? do |node|
129
+ if n = node.find(:set_flag)
130
+ # Inline instead of setting a flag if...
131
+ gated_by_set?(n.to_a[0], node2) && # The flag set by node1 is gating node2
132
+ n.to_a[1] == 'auto_generated' && # The flag has been generated and not specified by the user
133
+ n.to_a[0] !~ /_RAN$/ && # And don't compress RAN flags because they can be set by both on_fail and on_pass
134
+ !volatile?(n.to_a[0]) # And make sure the flag has not been marked as volatile
135
+ end
136
+ end
137
+ return true
138
+ end
139
+ end
140
+ false
141
+ end
142
+
143
+ def combine(node1, node2)
144
+ nodes_to_inline_on_pass_or_fail << node2
145
+ node1 = node1.updated(nil, process_all(node1.children))
146
+ nodes_to_inline_on_pass_or_fail.pop
147
+ node1
148
+ end
149
+
150
+ # node will always be an if_flag or unless_flag type node, guaranteed by the caller
151
+ #
152
+ # Returns true if flag matches the one supplied
153
+ #
154
+ # s(:if_flag, flag,
155
+ # s(:test, ...
156
+ #
157
+ # Also returns true if flag matches the one supplied, but it is nested within other flag conditions:
158
+ #
159
+ # s(:unless_flag, other_flag,
160
+ # s(:if_flag, other_flag2,
161
+ # s(:if_flag, flag,
162
+ # s(:test, ...
163
+ def gated_by_set?(flag, node)
164
+ (flag == node.to_a[0] && node.type == :if_flag) ||
165
+ (node.to_a.size == 2 && (node.to_a.last.type == :if_flag || node.to_a.last.type == :unless_flag) && gated_by_set?(flag, node.to_a.last))
166
+ end
167
+
168
+ # Returns the node with the run_flag clauses re-ordered to have the given flag of interest at the top.
169
+ #
170
+ # The caller guarantees the run_flag clause containing the given flag is present.
171
+ #
172
+ # For example, given this node:
173
+ #
174
+ # s(:unless_flag, "flag1",
175
+ # s(:if_flag, "ot_BEA7F3B_FAILED",
176
+ # s(:test,
177
+ # s(:object, <TestSuite: inner_test1_BEA7F3B>),
178
+ # s(:name, "inner_test1_BEA7F3B"),
179
+ # s(:number, 0),
180
+ # s(:id, "it1_BEA7F3B"),
181
+ # s(:on_fail,
182
+ # s(:render, "multi_bin;")))))
183
+ #
184
+ # Then this node would be returned when the flag of interest is ot_BEA7F3B_FAILED:
185
+ #
186
+ # s(:if_flag, "ot_BEA7F3B_FAILED",
187
+ # s(:unless_flag, "flag1",
188
+ # s(:test,
189
+ # s(:object, <TestSuite: inner_test1_BEA7F3B>),
190
+ # s(:name, "inner_test1_BEA7F3B"),
191
+ # s(:number, 0),
192
+ # s(:id, "it1_BEA7F3B"),
193
+ # s(:on_fail,
194
+ # s(:render, "multi_bin;")))))
195
+ def reorder_nested_run_flags(flag, node)
196
+ # If the run_flag we care about is already at the top, just return node
197
+ unless node.to_a[0] == flag && node.type == :if_flag
198
+ if_run_flag_to_remove << flag
199
+ node = node.updated(:if_flag, [flag] + [process(node)])
200
+ if_run_flag_to_remove.pop
201
+ end
202
+ node
203
+ end
204
+
205
+ def if_run_flag_to_remove
206
+ @if_run_flag_to_remove ||= []
207
+ end
208
+
209
+ def nodes_to_inline_on_pass_or_fail
210
+ @nodes_to_inline_on_pass_or_fail ||= []
211
+ end
212
+ end
213
+ end
214
+ end
@@ -0,0 +1,58 @@
1
+ module ATP
2
+ module Processors
3
+ # Gives every node their own individual wrapping of condition nodes. No attempt is made
4
+ # to identify or remove duplicate conditions in the wrapping, that will be done later by
5
+ # the RedundantConditionRemover.
6
+ class Flattener < Processor
7
+ def run(node)
8
+ @results = [[]]
9
+ @conditions = []
10
+ process(node)
11
+ node.updated(:flow, results)
12
+ end
13
+
14
+ def on_flow(node)
15
+ process_all(node.children)
16
+ end
17
+
18
+ # Handles the top-level flow nodes
19
+ def on_volatile(node)
20
+ results << node
21
+ end
22
+ alias_method :on_name, :on_volatile
23
+ alias_method :on_id, :on_volatile
24
+
25
+ def on_group(node)
26
+ @results << []
27
+ process_all(node.children)
28
+ nodes = @results.pop
29
+ results << node.updated(nil, nodes)
30
+ end
31
+
32
+ def on_condition_node(node)
33
+ flag, *nodes = *node
34
+ @conditions << node.updated(node.type, [flag])
35
+ process_all(nodes)
36
+ @conditions.pop
37
+ end
38
+ ATP::Flow::CONDITION_NODE_TYPES.each do |type|
39
+ alias_method "on_#{type}", :on_condition_node unless method_defined?("on_#{type}")
40
+ end
41
+
42
+ def handler_missing(node)
43
+ results << wrap_with_current_conditions(node)
44
+ end
45
+
46
+ def wrap_with_current_conditions(node)
47
+ @conditions.reverse_each do |condition|
48
+ node = condition.updated(nil, condition.children + [node])
49
+ end
50
+ node
51
+ end
52
+
53
+ def results
54
+ @results.last
55
+ end
56
+ end
57
+ end
58
+ end
@@ -17,8 +17,8 @@ module ATP
17
17
  end
18
18
  end
19
19
 
20
- def on_test_result(node)
21
- tid, state, nodes = *node
20
+ def on_if_failed(node)
21
+ tid, *nodes = *node
22
22
  if tid.is_a?(Array)
23
23
  tid = tid.map do |tid|
24
24
  if tid =~ /^extern/
@@ -32,9 +32,15 @@ module ATP
32
32
  tid = "#{tid}_#{id}"
33
33
  end
34
34
  end
35
- node.updated(nil, [tid, state] + [process(nodes)])
35
+ node.updated(nil, [tid] + process_all(nodes))
36
36
  end
37
- alias_method :on_test_executed, :on_test_result
37
+ alias_method :on_if_any_failed, :on_if_failed
38
+ alias_method :on_if_all_failed, :on_if_failed
39
+ alias_method :on_if_passed, :on_if_failed
40
+ alias_method :on_if_any_passed, :on_if_failed
41
+ alias_method :on_if_all_passed, :on_if_failed
42
+ alias_method :on_if_ran, :on_if_failed
43
+ alias_method :on_unless_ran, :on_if_failed
38
44
  end
39
45
  end
40
46
  end
@@ -0,0 +1,39 @@
1
+ module ATP
2
+ module Processors
3
+ # Removes most things from embedded on_pass/fail nodes and converts them to the equivalent
4
+ # on_passed/failed condition at the same level as the parent node
5
+ class OnPassFailRemover < Processor
6
+ def run(node)
7
+ process(node)
8
+ end
9
+
10
+ def on_test(node)
11
+ on_pass = node.find(:on_pass)
12
+ on_fail = node.find(:on_fail)
13
+ if on_pass || on_fail
14
+ id = node.find(:id)
15
+ unless id
16
+ fail 'Something has gone wrong, all nodes should have IDs by this point'
17
+ end
18
+ id = id.value
19
+ nodes = [node]
20
+ if on_fail && contains_anything_interesting?(on_fail)
21
+ nodes << node.updated(:if_failed, [id] + on_fail.children)
22
+ nodes[0] = nodes[0].remove(on_fail)
23
+ end
24
+ if on_pass && contains_anything_interesting?(on_pass)
25
+ nodes << node.updated(:if_passed, [id] + on_pass.children)
26
+ nodes[0] = nodes[0].remove(on_pass)
27
+ end
28
+ node.updated(:inline, nodes)
29
+ else
30
+ node.updated(nil, process_all(node.children))
31
+ end
32
+ end
33
+
34
+ def contains_anything_interesting?(node)
35
+ node.children.any? { |n| n.type != :set_result && n.type != :continue && n.type != :set_flag }
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,79 @@
1
+ module ATP
2
+ module Processors
3
+ # Ensures that all test nodes only ever set a flag once
4
+ class OneFlagPerTest < Processor
5
+ def run(node)
6
+ @build_table = true
7
+ @pass_table = {}
8
+ @fail_table = {}
9
+ process(node)
10
+ @counters = {}
11
+ @pass_table.each { |f, v| @counters[f] = 0 }
12
+ @fail_table.each { |f, v| @counters[f] = 0 }
13
+ @build_table = false
14
+ process(node)
15
+ end
16
+
17
+ def on_test(node)
18
+ on_pass = node.find(:on_pass)
19
+ on_fail = node.find(:on_fail)
20
+ if @build_table
21
+ if on_fail
22
+ on_fail.find_all(:set_flag).each do |n|
23
+ @fail_table[n.to_a[0]] ||= 0
24
+ @fail_table[n.to_a[0]] += 1
25
+ end
26
+ end
27
+ if on_pass
28
+ on_pass.find_all(:set_flag).each do |n|
29
+ @pass_table[n.to_a[0]] ||= 0
30
+ @pass_table[n.to_a[0]] += 1
31
+ end
32
+ end
33
+ else
34
+ to_be_set = {}
35
+ if on_fail
36
+ node = node.remove(on_fail)
37
+ on_fail.find_all(:set_flag).each do |set_flag|
38
+ old_flag = set_flag.to_a[0]
39
+ if @fail_table[old_flag] > 1
40
+ on_fail = on_fail.remove(set_flag)
41
+ new_flag = "#{old_flag}_#{@counters[old_flag]}"
42
+ @counters[old_flag] += 1
43
+ to_be_set[old_flag] = new_flag
44
+ c = set_flag.children.dup
45
+ c[0] = new_flag
46
+ set_flag = set_flag.updated(nil, c)
47
+ on_fail = on_fail.updated(nil, on_fail.children + [set_flag])
48
+ end
49
+ end
50
+ node = node.updated(nil, node.children + [on_fail])
51
+ end
52
+ if on_pass
53
+ node = node.remove(on_pass)
54
+ on_pass.find_all(:set_flag).each do |set_flag|
55
+ old_flag = set_flag.to_a[0]
56
+ if @pass_table[old_flag] > 1
57
+ on_pass = on_pass.remove(set_flag)
58
+ new_flag = "#{old_flag}_#{@counters[old_flag]}"
59
+ @counters[old_flag] += 1
60
+ to_be_set[old_flag] = new_flag
61
+ c = set_flag.children.dup
62
+ c[0] = new_flag
63
+ set_flag = set_flag.updated(nil, c)
64
+ on_pass = on_pass.updated(nil, on_pass.children + [set_flag])
65
+ end
66
+ end
67
+ node = node.updated(nil, node.children + [on_pass])
68
+ end
69
+ if to_be_set.empty?
70
+ node
71
+ else
72
+ nodes = to_be_set.map { |old, new| node.updated(:if_flag, [new, node.updated(:set_flag, [old, 'auto_generated'])]) }
73
+ node.updated(:inline, [node] + nodes)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -1,8 +1,8 @@
1
1
  module ATP
2
2
  module Processors
3
3
  # Modifies the AST by performing some basic clean up, mainly to sanitize
4
- # user input. For example it will ensure that all IDs are symbols, and that
5
- # all names are lower-cased strings.
4
+ # user input. For example it will ensure that all IDs and references are underscored
5
+ # and lower cased.
6
6
  class PreCleaner < Processor
7
7
  def initialize
8
8
  @group_ids = []
@@ -15,12 +15,17 @@ module ATP
15
15
  end
16
16
 
17
17
  # Make all ID references use the lower case symbols
18
- def on_test_executed(node)
19
- children = node.children.dup
20
- children[0] = clean(children[0])
21
- node.updated(nil, process_all(children))
18
+ def on_if_failed(node)
19
+ id, *children = *node
20
+ node.updated(nil, [clean(id)] + process_all(children))
22
21
  end
23
- alias_method :on_test_result, :on_test_executed
22
+ alias_method :on_if_passed, :on_if_failed
23
+ alias_method :on_if_any_failed, :on_if_failed
24
+ alias_method :on_if_all_failed, :on_if_failed
25
+ alias_method :on_if_any_passed, :on_if_failed
26
+ alias_method :on_if_all_passed, :on_if_failed
27
+ alias_method :on_if_ran, :on_if_failed
28
+ alias_method :on_unless_ran, :on_if_failed
24
29
 
25
30
  def on_group(node)
26
31
  if id = node.children.find { |n| n.type == :id }
@@ -51,7 +56,7 @@ module ATP
51
56
  if id.is_a?(Array)
52
57
  id.map { |i| clean(i) }
53
58
  else
54
- id.to_s.downcase.to_sym
59
+ id.to_s.symbolize.to_s
55
60
  end
56
61
  end
57
62
  end