atp 0.8.0 → 1.0.0

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