atp 0.1.0 → 0.2.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 +4 -4
- data/config/{environment.rb → boot.rb} +0 -0
- data/config/commands.rb +10 -10
- data/config/version.rb +1 -1
- data/lib/atp.rb +53 -9
- data/lib/atp/and.rb +11 -0
- data/lib/atp/ast/builder.rb +214 -0
- data/lib/atp/ast/extractor.rb +26 -0
- data/lib/atp/ast/factories.rb +13 -0
- data/lib/atp/ast/node.rb +71 -0
- data/lib/atp/flow.rb +140 -0
- data/lib/atp/formatter.rb +20 -0
- data/lib/atp/formatters/basic.rb +18 -0
- data/lib/atp/formatters/datalog.rb +20 -0
- data/lib/atp/not.rb +13 -0
- data/lib/atp/or.rb +11 -0
- data/lib/atp/parser.rb +26 -0
- data/lib/atp/processor.rb +38 -0
- data/lib/atp/processors/condition.rb +174 -0
- data/lib/atp/processors/post_cleaner.rb +43 -0
- data/lib/atp/processors/pre_cleaner.rb +43 -0
- data/lib/atp/processors/relationship.rb +154 -0
- data/lib/atp/program.rb +27 -0
- data/lib/atp/runner.rb +53 -0
- data/lib/atp/validators/condition.rb +4 -0
- metadata +55 -10
- data/config/development.rb +0 -12
- data/config/users.rb +0 -29
- data/lib/atp/top_level.rb +0 -12
data/lib/atp/flow.rb
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
module ATP
|
2
|
+
# Implements the main user API for building and interacting
|
3
|
+
# with an abstract test program
|
4
|
+
class Flow
|
5
|
+
attr_reader :program
|
6
|
+
# Returns the raw AST
|
7
|
+
attr_reader :raw
|
8
|
+
|
9
|
+
def initialize(program)
|
10
|
+
@program = program
|
11
|
+
@builder = AST::Builder.new
|
12
|
+
@raw = builder.flow
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns a processed/optimized AST, this is the one that should be
|
16
|
+
# used to build and represent the given test flow
|
17
|
+
def ast
|
18
|
+
ast = Processors::PreCleaner.new.process(raw)
|
19
|
+
ast = Processors::Condition.new.process(ast)
|
20
|
+
ast = Processors::Relationship.new.process(ast)
|
21
|
+
ast = Processors::PostCleaner.new.process(ast)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Group all tests generated within the given block
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# flow.group "RAM Tests" do
|
28
|
+
# flow.test ...
|
29
|
+
# flow.test ...
|
30
|
+
# end
|
31
|
+
def group(name, options = {})
|
32
|
+
open_groups.push([])
|
33
|
+
yield
|
34
|
+
append builder.group(name, open_groups.pop, options)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Add a test line to the flow
|
38
|
+
#
|
39
|
+
# @param [String, Symbol] the name of the test
|
40
|
+
# @param [Hash] options a hash to describe the test's attributes
|
41
|
+
# @option options [Symbol] :id A unique test ID
|
42
|
+
# @option options [String] :description A description of what the test does, usually formatted in markdown
|
43
|
+
# @option options [Hash] :on_fail What action to take if the test fails, e.g. assign a bin
|
44
|
+
# @option options [Hash] :on_pass What action to take if the test passes
|
45
|
+
# @option options [Hash] :conditions What conditions must be met to execute the test
|
46
|
+
def test(instance, options = {})
|
47
|
+
r = options.delete(:return)
|
48
|
+
if options[:context] == :current
|
49
|
+
options[:conditions] = builder.context[:conditions]
|
50
|
+
end
|
51
|
+
# Allows any continue, bin, or soft bin argument passed in at the options top-level to be assumed
|
52
|
+
# to be the action to take if the test fails
|
53
|
+
if b = options.delete(:bin)
|
54
|
+
options[:on_fail] ||= {}
|
55
|
+
options[:on_fail][:bin] = b
|
56
|
+
end
|
57
|
+
if b = options.delete(:softbin) || b = options.delete(:sbin) || b = options.delete(:soft_bin)
|
58
|
+
options[:on_fail] ||= {}
|
59
|
+
options[:on_fail][:softbin] = b
|
60
|
+
end
|
61
|
+
if options.delete(:continue)
|
62
|
+
options[:on_fail] ||= {}
|
63
|
+
options[:on_fail][:continue] = true
|
64
|
+
end
|
65
|
+
builder.new_context
|
66
|
+
|
67
|
+
t = builder.test(instance, options)
|
68
|
+
unless options[:context] == :current
|
69
|
+
open_conditions.each do |conditions|
|
70
|
+
t = builder.apply_conditions(t, conditions)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
append(t) unless r
|
74
|
+
t
|
75
|
+
end
|
76
|
+
|
77
|
+
def bin(number, options = {})
|
78
|
+
fail 'A :type option set to :pass or :fail is required when calling bin' unless options[:type]
|
79
|
+
options[:bin] = number
|
80
|
+
options[:softbin] ||= options[:soft_bin] || options[:sbin]
|
81
|
+
append builder.set_result(options[:type], options)
|
82
|
+
end
|
83
|
+
|
84
|
+
def cz(instance, cz_setup, options = {})
|
85
|
+
options[:return] = true
|
86
|
+
append(builder.cz(cz_setup, test(instance, options)))
|
87
|
+
end
|
88
|
+
alias_method :characterize, :cz
|
89
|
+
|
90
|
+
# Append a log message line to the flow
|
91
|
+
def log(message, options = {})
|
92
|
+
append builder.log(message)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Insert explicitly rendered content in to the flow
|
96
|
+
def render(str, options = {})
|
97
|
+
append builder.render(str)
|
98
|
+
end
|
99
|
+
|
100
|
+
def with_condition(options)
|
101
|
+
open_conditions.push(options)
|
102
|
+
yield
|
103
|
+
open_conditions.pop
|
104
|
+
end
|
105
|
+
alias_method :with_conditions, :with_condition
|
106
|
+
|
107
|
+
# Execute the given flow in the console
|
108
|
+
def run(options = {})
|
109
|
+
Formatters::Datalog.run_and_format(ast, options)
|
110
|
+
nil
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
# For testing
|
116
|
+
def raw=(ast)
|
117
|
+
@raw = ast
|
118
|
+
end
|
119
|
+
|
120
|
+
def open_conditions
|
121
|
+
@open_conditions ||= []
|
122
|
+
end
|
123
|
+
|
124
|
+
def open_groups
|
125
|
+
@open_groups ||= []
|
126
|
+
end
|
127
|
+
|
128
|
+
def append(node)
|
129
|
+
if open_groups.empty?
|
130
|
+
@raw = @raw.updated(nil, @raw.children + [node])
|
131
|
+
else
|
132
|
+
open_groups.last << node
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def builder
|
137
|
+
@builder
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module ATP
|
2
|
+
class Formatter < Processor
|
3
|
+
def format(node, options = {})
|
4
|
+
process(node)
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.format(node, options = {})
|
8
|
+
new.format(node, options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.run_and_format(node, options = {})
|
12
|
+
ast = Runner.new.run(node, options)
|
13
|
+
format(ast, options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.run(*args)
|
17
|
+
run_and_format(*args)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module ATP
|
2
|
+
module Formatters
|
3
|
+
# Returns the executed flow as a string of test names. This
|
4
|
+
# is mainly intended to be used for testing the runner.
|
5
|
+
class Basic < Formatter
|
6
|
+
def format(node, options = {})
|
7
|
+
@output = ''
|
8
|
+
process(node)
|
9
|
+
@output
|
10
|
+
end
|
11
|
+
|
12
|
+
def on_test(node)
|
13
|
+
@output += node.to_h[:name][0]
|
14
|
+
@output += "\n"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module ATP
|
2
|
+
module Formatters
|
3
|
+
# Outputs the given AST to something resembling an ATE datalog,
|
4
|
+
# this can optionally be rendered to a file or the console (the default).
|
5
|
+
class Datalog < Formatter
|
6
|
+
def on_flow(node)
|
7
|
+
puts 'Number Result Test Name Pin Channel Low Measured High Force Loc'
|
8
|
+
process_all(node.children)
|
9
|
+
end
|
10
|
+
|
11
|
+
def on_test(node)
|
12
|
+
t = node.to_h
|
13
|
+
str = "#{t[:number]}".ljust(11)
|
14
|
+
str += "#{t[:failed] ? 'FAIL' : 'PASS'}".ljust(9)
|
15
|
+
str += "#{t[:name][0]}".ljust(20)
|
16
|
+
puts str
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/atp/not.rb
ADDED
data/lib/atp/or.rb
ADDED
data/lib/atp/parser.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'sexpistol'
|
2
|
+
module ATP
|
3
|
+
class Parser < Sexpistol
|
4
|
+
def initialize
|
5
|
+
self.ruby_keyword_literals = true
|
6
|
+
end
|
7
|
+
|
8
|
+
def string_to_ast(string)
|
9
|
+
to_sexp(parse_string(string))
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_sexp(ast_array)
|
13
|
+
children = ast_array.map do |item|
|
14
|
+
if item.is_a?(Array)
|
15
|
+
to_sexp(item)
|
16
|
+
else
|
17
|
+
item
|
18
|
+
end
|
19
|
+
end
|
20
|
+
type = children.shift
|
21
|
+
return type if type.is_a?(ATP::AST::Node)
|
22
|
+
type = type.to_s.gsub('-', '_').to_sym
|
23
|
+
ATP::AST::Node.new(type, children)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'ast'
|
2
|
+
module ATP
|
3
|
+
# The base processor, this provides a default handler for
|
4
|
+
# all node types and will not make any changes to the AST,
|
5
|
+
# i.e. an equivalent AST will be returned by the process method.
|
6
|
+
#
|
7
|
+
# Child classes of this should be used to implement additional
|
8
|
+
# processors to modify or otherwise work with the AST.
|
9
|
+
#
|
10
|
+
# @see http://www.rubydoc.info/gems/ast/2.0.0/AST/Processor
|
11
|
+
class Processor
|
12
|
+
include ::AST::Processor::Mixin
|
13
|
+
|
14
|
+
def process(node)
|
15
|
+
if node.respond_to?(:to_ast)
|
16
|
+
super(node)
|
17
|
+
else
|
18
|
+
node
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def handler_missing(node)
|
23
|
+
node.updated(nil, process_all(node.children))
|
24
|
+
end
|
25
|
+
|
26
|
+
def n(type, children)
|
27
|
+
ATP::AST::Node.new(type, children)
|
28
|
+
end
|
29
|
+
|
30
|
+
def n0(type)
|
31
|
+
n(type, [])
|
32
|
+
end
|
33
|
+
|
34
|
+
def n1(type, arg)
|
35
|
+
n(type, [arg])
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
module ATP
|
2
|
+
module Processors
|
3
|
+
# This optimizes the condition nodes such that any adjacent flow nodes that
|
4
|
+
# have the same condition, will be grouped together under a single condition
|
5
|
+
# wrapper.
|
6
|
+
#
|
7
|
+
# For example this AST:
|
8
|
+
#
|
9
|
+
# (flow
|
10
|
+
# (group "g1"
|
11
|
+
# (test
|
12
|
+
# (name "test1"))
|
13
|
+
# (flow-flag "bitmap" true
|
14
|
+
# (test
|
15
|
+
# (name "test2"))))
|
16
|
+
# (flow-flag "bitmap" true
|
17
|
+
# (group "g1"
|
18
|
+
# (flow-flag "x" true
|
19
|
+
# (test
|
20
|
+
# (name "test3")))
|
21
|
+
# (flow-flag "y" true
|
22
|
+
# (flow-flag "x" true
|
23
|
+
# (test
|
24
|
+
# (name "test4")))))))
|
25
|
+
#
|
26
|
+
# Will be optimized to this:
|
27
|
+
#
|
28
|
+
# (flow
|
29
|
+
# (group "g1"
|
30
|
+
# (test
|
31
|
+
# (name "test1"))
|
32
|
+
# (flow-flag "bitmap" true
|
33
|
+
# (test
|
34
|
+
# (name "test2"))
|
35
|
+
# (flow-flag "x" true
|
36
|
+
# (test
|
37
|
+
# (name "test3"))
|
38
|
+
# (flow-flag "y" true
|
39
|
+
# (test
|
40
|
+
# (name "test4")))))))
|
41
|
+
#
|
42
|
+
class Condition < Processor
|
43
|
+
CONDITION_NODES = [:flow_flag, :test_result, :test_executed, :group, :job]
|
44
|
+
|
45
|
+
def process(node)
|
46
|
+
# Bit of a hack - To get all of the nested conditions optimized away it is necessary
|
47
|
+
# to execute this recursively a few times. This guard ensures that the recursion is
|
48
|
+
# only performed on the top-level and not on every process operation.
|
49
|
+
if @top_level_called
|
50
|
+
super
|
51
|
+
else
|
52
|
+
@top_level_called = true
|
53
|
+
ast1 = nil
|
54
|
+
ast2 = node
|
55
|
+
while ast1 != ast2
|
56
|
+
ast1 = super(ast2)
|
57
|
+
ast2 = super(ast1)
|
58
|
+
end
|
59
|
+
@top_level_called = false
|
60
|
+
ast1
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def on_boolean_condition(node)
|
65
|
+
children = node.children.dup
|
66
|
+
name = children.shift
|
67
|
+
state = children.shift
|
68
|
+
children = optimize_siblings(n(:temp, children))
|
69
|
+
if condition_to_be_removed?(node)
|
70
|
+
process_all(children)
|
71
|
+
else
|
72
|
+
node.updated(nil, [name, state] + process_all(children))
|
73
|
+
end
|
74
|
+
end
|
75
|
+
alias_method :on_flow_flag, :on_boolean_condition
|
76
|
+
alias_method :on_test_result, :on_boolean_condition
|
77
|
+
alias_method :on_test_executed, :on_boolean_condition
|
78
|
+
|
79
|
+
def on_condition(node)
|
80
|
+
children = node.children.dup
|
81
|
+
name = children.shift
|
82
|
+
children = optimize_siblings(n(:temp, children))
|
83
|
+
if condition_to_be_removed?(node)
|
84
|
+
process_all(children)
|
85
|
+
else
|
86
|
+
node.updated(nil, [name] + process_all(children))
|
87
|
+
end
|
88
|
+
end
|
89
|
+
alias_method :on_group, :on_condition
|
90
|
+
alias_method :on_job, :on_condition
|
91
|
+
|
92
|
+
# Returns true if the given node contains the given condition within
|
93
|
+
# its immediate children
|
94
|
+
def has_condition?(condition, node)
|
95
|
+
([node] + node.children.to_a).any? do |n|
|
96
|
+
if n.is_a?(ATP::AST::Node)
|
97
|
+
equal_conditions?(condition, n)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def condition_to_be_removed?(node)
|
103
|
+
remove_condition.last && equal_conditions?(remove_condition.last, node)
|
104
|
+
end
|
105
|
+
|
106
|
+
def equal_conditions?(node1, node2)
|
107
|
+
if node1.type == node2.type
|
108
|
+
if node1.type == :group || node1.type == :job
|
109
|
+
node1.to_a.take(1) == node2.to_a.take(1)
|
110
|
+
else
|
111
|
+
node1.to_a.take(2) == node2.to_a.take(2)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def condition?(node)
|
117
|
+
node.is_a?(ATP::AST::Node) && CONDITION_NODES.include?(node.type)
|
118
|
+
end
|
119
|
+
|
120
|
+
def on_flow(node)
|
121
|
+
node.updated(nil, optimize_siblings(node))
|
122
|
+
end
|
123
|
+
|
124
|
+
def on_members(node)
|
125
|
+
node.updated(nil, optimize_siblings(node))
|
126
|
+
end
|
127
|
+
|
128
|
+
def optimize_siblings(top_node)
|
129
|
+
children = []
|
130
|
+
unprocessed_children = []
|
131
|
+
current = nil
|
132
|
+
last = top_node.children.size - 1
|
133
|
+
top_node.to_a.each_with_index do |node, i|
|
134
|
+
# If a condition has been identified in a previous node
|
135
|
+
if current
|
136
|
+
process_nodes = false
|
137
|
+
# If this node has the current condition, then buffer it for later processing
|
138
|
+
# and continue to the next node
|
139
|
+
if has_condition?(current, node)
|
140
|
+
unprocessed_children << node
|
141
|
+
node = nil
|
142
|
+
else
|
143
|
+
process_nodes = true
|
144
|
+
end
|
145
|
+
if process_nodes || i == last
|
146
|
+
remove_condition << current
|
147
|
+
current_children = current.children + [process_all(unprocessed_children)].flatten
|
148
|
+
unprocessed_children = []
|
149
|
+
remove_condition.pop
|
150
|
+
children << process(current.updated(nil, current_children))
|
151
|
+
if node && (!condition?(node) || i == last)
|
152
|
+
current = nil
|
153
|
+
children << process(node)
|
154
|
+
else
|
155
|
+
current = node
|
156
|
+
end
|
157
|
+
end
|
158
|
+
else
|
159
|
+
if condition?(node) && i != last
|
160
|
+
current = node
|
161
|
+
else
|
162
|
+
children << process(node)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
children.flatten
|
167
|
+
end
|
168
|
+
|
169
|
+
def remove_condition
|
170
|
+
@remove_condition ||= []
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|