atp 0.2.0 → 0.2.1

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: 7ca6eaefd174b135c5b39a8b2d287f24cd890814
4
- data.tar.gz: 3c379de6d014a88c5dbec902ec85eda2d4d1abd6
3
+ metadata.gz: 0318ad5ca67b39da3b87b038434eaa85b1ee2a1d
4
+ data.tar.gz: 63bdc11c210bba2219097d14b3c5aa55678a8c8f
5
5
  SHA512:
6
- metadata.gz: ec297a8c950f05a49eaa17c359d5c35378aea3e9a1e39f88f3c72906968658dc22e61590357f83b462f25dab699a1577d7d334efdb26d126b0f3694a159eaa9f
7
- data.tar.gz: 1fc8899484368c2fc172801ba5bae1968fd9488d52420d0f0c30e1275a9db41783ab2886536317ca5744e2b7222badee59a9e6d5006d373b827f6e90d3bb6d80
6
+ metadata.gz: ae4da77cd22cc197e3ecf80af390de270b71c6a00cfcae3c49345156b9840c5f6429520707c29c36dffb89d54ec50b8af710257c93f23b872d44b5a10fb8aad6
7
+ data.tar.gz: 6ee881a43ee9cf5015860b50719d6d07c461f545180d967319d8ada891a36cd47adf8ac48a5ef277c79494bfbc0a54dfdddb77d1bbaac77a70dc8a6d67b63f24
@@ -1,7 +1,7 @@
1
1
  module ATP
2
2
  MAJOR = 0
3
3
  MINOR = 2
4
- BUGFIX = 0
4
+ BUGFIX = 1
5
5
  DEV = nil
6
6
 
7
7
  VERSION = [MAJOR, MINOR, BUGFIX].join(".") + (DEV ? ".pre#{DEV}" : '')
data/lib/atp.rb CHANGED
@@ -1,9 +1,11 @@
1
1
  require 'origen'
2
2
  require_relative '../config/application.rb'
3
+
3
4
  module ATP
4
5
  autoload :Program, 'atp/program'
5
6
  autoload :Flow, 'atp/flow'
6
7
  autoload :Processor, 'atp/processor'
8
+ autoload :Validator, 'atp/validator'
7
9
  autoload :Runner, 'atp/runner'
8
10
  autoload :Formatter, 'atp/formatter'
9
11
  autoload :Parser, 'atp/parser'
@@ -22,6 +24,7 @@ module ATP
22
24
  # and to implement the flow control API
23
25
  module Processors
24
26
  autoload :Condition, 'atp/processors/condition'
27
+ autoload :ConditionExtractor, 'atp/processors/condition_extractor'
25
28
  autoload :Relationship, 'atp/processors/relationship'
26
29
  autoload :PreCleaner, 'atp/processors/pre_cleaner'
27
30
  autoload :PostCleaner, 'atp/processors/post_cleaner'
@@ -34,6 +37,8 @@ module ATP
34
37
  # Validators are run on the processed AST to check it for common errors or
35
38
  # logical issues that will prevent it being rendered to a test program format
36
39
  module Validators
40
+ autoload :DuplicateIDs, 'atp/validators/duplicate_ids'
41
+ autoload :MissingIDs, 'atp/validators/missing_ids'
37
42
  autoload :Condition, 'atp/validators/condition'
38
43
  end
39
44
 
@@ -4,6 +4,7 @@ module ATP
4
4
  include Factories
5
5
 
6
6
  attr_reader :context
7
+ attr_accessor :source_file, :source_line_number
7
8
 
8
9
  def flow
9
10
  n0(:flow)
@@ -13,8 +14,13 @@ module ATP
13
14
  n(:name, str.to_s)
14
15
  end
15
16
 
16
- def log(str)
17
- n(:log, str.to_s)
17
+ def log(str, options = {})
18
+ test = n(:log, str.to_s)
19
+ if options[:conditions]
20
+ apply_conditions(test, options[:conditions])
21
+ else
22
+ test
23
+ end
18
24
  end
19
25
 
20
26
  def render(str)
@@ -41,6 +47,24 @@ module ATP
41
47
  n(:test_executed, id, executed, node)
42
48
  end
43
49
 
50
+ def enable_flow_flag(var, options = {})
51
+ test = n(:enable_flow_flag, var)
52
+ if options[:conditions]
53
+ apply_conditions(test, options[:conditions])
54
+ else
55
+ test
56
+ end
57
+ end
58
+
59
+ def disable_flow_flag(var, options = {})
60
+ test = n(:disable_flow_flag, var)
61
+ if options[:conditions]
62
+ apply_conditions(test, options[:conditions])
63
+ else
64
+ test
65
+ end
66
+ end
67
+
44
68
  def group(group_name, nodes, options = {})
45
69
  children = [name(group_name)]
46
70
 
@@ -59,8 +83,13 @@ module ATP
59
83
  end
60
84
  end
61
85
 
62
- def cz(setup, node)
63
- n(:cz, setup, node)
86
+ def cz(setup, node, options = {})
87
+ test = n(:cz, setup, node)
88
+ if options[:conditions]
89
+ apply_conditions(test, options[:conditions])
90
+ else
91
+ test
92
+ end
64
93
  end
65
94
 
66
95
  def new_context
@@ -199,7 +228,13 @@ module ATP
199
228
  children << n(:bin, options[:bin]) if options[:bin]
200
229
  children << n(:softbin, options[:softbin]) if options[:softbin]
201
230
  children << n(:description, options[:description]) if options[:description]
202
- n(:set_result, *children)
231
+ result = n(:set_result, *children)
232
+
233
+ if options[:conditions]
234
+ apply_conditions(result, options[:conditions])
235
+ else
236
+ result
237
+ end
203
238
  end
204
239
 
205
240
  def number(val)
@@ -2,11 +2,14 @@ module ATP
2
2
  module AST
3
3
  module Factories
4
4
  def n(type, *children)
5
- ATP::AST::Node.new(type, children)
5
+ options = children.last.is_a?(Hash) ? children.pop : {}
6
+ options[:file] ||= options.delete(:source_file) || try(:source_file)
7
+ options[:line_number] ||= options.delete(:source_line_number) || try(:source_line_number)
8
+ ATP::AST::Node.new(type, children, options)
6
9
  end
7
10
 
8
- def n0(type)
9
- ATP::AST::Node.new(type, [])
11
+ def n0(type, options = {})
12
+ n(type, options)
10
13
  end
11
14
  end
12
15
  end
@@ -4,6 +4,8 @@ module ATP
4
4
  class Node < ::AST::Node
5
5
  include Factories
6
6
 
7
+ attr_reader :file, :line_number
8
+
7
9
  def initialize(type, children = [], properties = {})
8
10
  # Always use strings instead of symbols in the AST, makes serializing
9
11
  # back and forward to a string easier
@@ -11,6 +13,14 @@ module ATP
11
13
  super type, children, properties
12
14
  end
13
15
 
16
+ def source
17
+ if file
18
+ "#{file}:#{line_number}"
19
+ else
20
+ '<Sorry, lost the source file info, please include an example if you report as a bug>'
21
+ end
22
+ end
23
+
14
24
  # Create a new node from the given S-expression (a string)
15
25
  def self.from_sexp(sexp)
16
26
  @parser ||= Parser.new
@@ -2,12 +2,13 @@ module ATP
2
2
  # Implements the main user API for building and interacting
3
3
  # with an abstract test program
4
4
  class Flow
5
- attr_reader :program
5
+ attr_reader :program, :name
6
6
  # Returns the raw AST
7
7
  attr_reader :raw
8
8
 
9
- def initialize(program)
9
+ def initialize(program, name = nil)
10
10
  @program = program
11
+ @name = name
11
12
  @builder = AST::Builder.new
12
13
  @raw = builder.flow
13
14
  end
@@ -16,6 +17,8 @@ module ATP
16
17
  # used to build and represent the given test flow
17
18
  def ast
18
19
  ast = Processors::PreCleaner.new.process(raw)
20
+ Validators::DuplicateIDs.new(self).process(ast)
21
+ Validators::MissingIDs.new(self).process(ast)
19
22
  ast = Processors::Condition.new.process(ast)
20
23
  ast = Processors::Relationship.new.process(ast)
21
24
  ast = Processors::PostCleaner.new.process(ast)
@@ -44,60 +47,86 @@ module ATP
44
47
  # @option options [Hash] :on_pass What action to take if the test passes
45
48
  # @option options [Hash] :conditions What conditions must be met to execute the test
46
49
  def test(instance, options = {})
50
+ extract_meta!(options)
47
51
  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)
52
+ t = apply_open_conditions(options) do |options|
53
+ # Allows any continue, bin, or soft bin argument passed in at the options top-level to be assumed
54
+ # to be the action to take if the test fails
55
+ if b = options.delete(:bin)
56
+ options[:on_fail] ||= {}
57
+ options[:on_fail][:bin] = b
58
+ end
59
+ if b = options.delete(:softbin) || b = options.delete(:sbin) || b = options.delete(:soft_bin)
60
+ options[:on_fail] ||= {}
61
+ options[:on_fail][:softbin] = b
71
62
  end
63
+ if options.delete(:continue)
64
+ options[:on_fail] ||= {}
65
+ options[:on_fail][:continue] = true
66
+ end
67
+ builder.test(instance, options)
72
68
  end
73
69
  append(t) unless r
74
70
  t
75
71
  end
76
72
 
77
73
  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)
74
+ extract_meta!(options)
75
+ t = apply_open_conditions(options) do |options|
76
+ fail 'A :type option set to :pass or :fail is required when calling bin' unless options[:type]
77
+ options[:bin] = number
78
+ options[:softbin] ||= options[:soft_bin] || options[:sbin]
79
+ builder.set_result(options[:type], options)
80
+ end
81
+ append(t)
82
82
  end
83
83
 
84
84
  def cz(instance, cz_setup, options = {})
85
- options[:return] = true
86
- append(builder.cz(cz_setup, test(instance, options)))
85
+ extract_meta!(options)
86
+ t = apply_open_conditions(options) do |options|
87
+ conditions = options.delete(:conditions)
88
+ options[:return] = true
89
+ builder.cz(cz_setup, test(instance, options), conditions: conditions)
90
+ end
91
+ append(t)
87
92
  end
88
93
  alias_method :characterize, :cz
89
94
 
90
95
  # Append a log message line to the flow
91
96
  def log(message, options = {})
92
- append builder.log(message)
97
+ extract_meta!(options)
98
+ t = apply_open_conditions(options) do |options|
99
+ builder.log(message, options)
100
+ end
101
+ append(t)
102
+ end
103
+
104
+ # Enable a flow control variable
105
+ def enable(var, options = {})
106
+ extract_meta!(options)
107
+ t = apply_open_conditions(options) do |options|
108
+ builder.enable_flow_flag(var, options)
109
+ end
110
+ append(t)
111
+ end
112
+
113
+ # Disable a flow control variable
114
+ def disable(var, options = {})
115
+ extract_meta!(options)
116
+ t = apply_open_conditions(options) do |options|
117
+ builder.disable_flow_flag(var, options)
118
+ end
119
+ append(t)
93
120
  end
94
121
 
95
122
  # Insert explicitly rendered content in to the flow
96
123
  def render(str, options = {})
124
+ extract_meta!(options)
97
125
  append builder.render(str)
98
126
  end
99
127
 
100
128
  def with_condition(options)
129
+ extract_meta!(options)
101
130
  open_conditions.push(options)
102
131
  yield
103
132
  open_conditions.pop
@@ -112,6 +141,25 @@ module ATP
112
141
 
113
142
  private
114
143
 
144
+ def apply_open_conditions(options)
145
+ if options[:context] == :current
146
+ options[:conditions] = builder.context[:conditions]
147
+ end
148
+ builder.new_context
149
+ t = yield(options)
150
+ unless options[:context] == :current
151
+ open_conditions.each do |conditions|
152
+ t = builder.apply_conditions(t, conditions)
153
+ end
154
+ end
155
+ t
156
+ end
157
+
158
+ def extract_meta!(options)
159
+ builder.source_file = options.delete(:source_file) if options[:source_file]
160
+ builder.source_line_number = options.delete(:source_line_number) if options[:source_line_number]
161
+ end
162
+
115
163
  # For testing
116
164
  def raw=(ast)
117
165
  @raw = ast
@@ -11,6 +11,10 @@ module ATP
11
11
  class Processor
12
12
  include ::AST::Processor::Mixin
13
13
 
14
+ def run(node)
15
+ process(node)
16
+ end
17
+
14
18
  def process(node)
15
19
  if node.respond_to?(:to_ast)
16
20
  super(node)
@@ -65,7 +65,9 @@ module ATP
65
65
  children = node.children.dup
66
66
  name = children.shift
67
67
  state = children.shift
68
+ remove_condition << node
68
69
  children = optimize_siblings(n(:temp, children))
70
+ remove_condition.pop
69
71
  if condition_to_be_removed?(node)
70
72
  process_all(children)
71
73
  else
@@ -79,7 +81,9 @@ module ATP
79
81
  def on_condition(node)
80
82
  children = node.children.dup
81
83
  name = children.shift
84
+ remove_condition << node
82
85
  children = optimize_siblings(n(:temp, children))
86
+ remove_condition.pop
83
87
  if condition_to_be_removed?(node)
84
88
  process_all(children)
85
89
  else
@@ -100,7 +104,7 @@ module ATP
100
104
  end
101
105
 
102
106
  def condition_to_be_removed?(node)
103
- remove_condition.last && equal_conditions?(remove_condition.last, node)
107
+ remove_condition.any? { |c| equal_conditions?(c, node) }
104
108
  end
105
109
 
106
110
  def equal_conditions?(node1, node2)
@@ -118,11 +122,17 @@ module ATP
118
122
  end
119
123
 
120
124
  def on_flow(node)
121
- node.updated(nil, optimize_siblings(node))
125
+ # The extract_common_embedded_conditions method can probably do the whole job,
126
+ # but it might get a little complicated with regards to optimizing adjacent groups,
127
+ # so have left the original logic to have the first crack and deal with the groups
128
+ # for now.
129
+ nodes = optimize_siblings(node)
130
+ nodes = extract_common_embedded_conditions(nodes)
131
+ node.updated(nil, nodes)
122
132
  end
123
133
 
124
134
  def on_members(node)
125
- node.updated(nil, optimize_siblings(node))
135
+ node.updated(nil, extract_common_embedded_conditions(optimize_siblings(node)))
126
136
  end
127
137
 
128
138
  def optimize_siblings(top_node)
@@ -166,6 +176,46 @@ module ATP
166
176
  children.flatten
167
177
  end
168
178
 
179
+ def extract_common_embedded_conditions(nodes)
180
+ nodes = [nodes] unless nodes.is_a?(Array)
181
+ result = []
182
+ cond_a = nil
183
+ test_a = nil
184
+ ConditionExtractor.new.run(nodes).each do |cond_b, test_b|
185
+ if cond_a
186
+ common = cond_a & cond_b
187
+ if common.empty?
188
+ result << combine(cond_a, extract_common_embedded_conditions(test_a))
189
+ cond_a = cond_b
190
+ test_a = test_b
191
+ else
192
+ a = combine(cond_a - common, test_a)
193
+ b = combine(cond_b - common, test_b)
194
+ cond_a = common
195
+ test_a = [a, b].flatten
196
+ end
197
+ else
198
+ cond_a = cond_b
199
+ test_a = test_b
200
+ end
201
+ end
202
+ if nodes == [test_a]
203
+ nodes
204
+ else
205
+ result << combine(cond_a, extract_common_embedded_conditions(test_a))
206
+ result.flatten
207
+ end
208
+ end
209
+
210
+ def combine(conditions, node)
211
+ if conditions && !conditions.empty?
212
+ conditions.reverse_each do |n|
213
+ node = n.updated(nil, n.children + (node.is_a?(Array) ? node : [node]))
214
+ end
215
+ end
216
+ node
217
+ end
218
+
169
219
  def remove_condition
170
220
  @remove_condition ||= []
171
221
  end
@@ -0,0 +1,46 @@
1
+ module ATP
2
+ module Processors
3
+ class ConditionExtractor < Processor
4
+ attr_reader :results, :conditions
5
+
6
+ def run(nodes)
7
+ @results = []
8
+ @conditions = []
9
+ process_all(nodes)
10
+ @results
11
+ end
12
+
13
+ def on_boolean_condition(node)
14
+ children = node.children.dup
15
+ name = children.shift
16
+ state = children.shift
17
+ conditions << node.updated(nil, [name, state])
18
+ process_all(children)
19
+ conditions.pop
20
+ end
21
+ alias_method :on_flow_flag, :on_boolean_condition
22
+ alias_method :on_test_result, :on_boolean_condition
23
+ alias_method :on_test_executed, :on_boolean_condition
24
+
25
+ def on_condition(node)
26
+ children = node.children.dup
27
+ name = children.shift
28
+ conditions << node.updated(nil, [name])
29
+ process_all(children)
30
+ conditions.pop
31
+ end
32
+ alias_method :on_job, :on_condition
33
+
34
+ def on_test(node)
35
+ results << [conditions.uniq, node]
36
+ end
37
+ alias_method :on_group, :on_test
38
+ alias_method :on_log, :on_test
39
+ alias_method :on_enable_flow_flag, :on_test
40
+ alias_method :on_disable_flow_flag, :on_test
41
+ alias_method :on_cz, :on_test
42
+ alias_method :on_set_result, :on_test
43
+ alias_method :on_render, :on_test
44
+ end
45
+ end
46
+ end
@@ -8,12 +8,20 @@ module ATP
8
8
  @group_ids = []
9
9
  end
10
10
 
11
+ # Make all IDs lower cased symbols
11
12
  def on_id(node)
12
- id = node.to_a.first
13
- id = id.to_s.downcase.to_sym
14
- node.updated(nil, [id])
13
+ id = node.to_a[0]
14
+ node.updated(nil, [clean(id)])
15
15
  end
16
16
 
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))
22
+ end
23
+ alias_method :on_test_result, :on_test_executed
24
+
17
25
  def on_group(node)
18
26
  if id = node.children.find { |n| n.type == :id }
19
27
  @group_ids << process(id).value
@@ -38,6 +46,14 @@ module ATP
38
46
  end
39
47
  node.updated(nil, process_all(children))
40
48
  end
49
+
50
+ def clean(id)
51
+ if id.is_a?(Array)
52
+ id.map { |i| clean(i) }
53
+ else
54
+ id.to_s.downcase.to_sym
55
+ end
56
+ end
41
57
  end
42
58
  end
43
59
  end
@@ -2,7 +2,7 @@ module ATP
2
2
  # Program is the top-level container for a collection of test flows
3
3
  class Program
4
4
  def flow(name)
5
- flows[name] ||= Flow.new(self)
5
+ flows[name] ||= Flow.new(self, name)
6
6
  end
7
7
 
8
8
  def flows
@@ -0,0 +1,24 @@
1
+ require 'ast'
2
+ module ATP
3
+ class Validator < Processor
4
+ attr_reader :flow
5
+
6
+ def initialize(flow)
7
+ @flow = flow
8
+ end
9
+
10
+ def process(node)
11
+ if @top_level_called
12
+ super
13
+ else
14
+ @top_level_called = true
15
+ setup
16
+ super(node)
17
+ exit 1 if on_completion
18
+ end
19
+ end
20
+
21
+ def setup
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,32 @@
1
+ module ATP
2
+ module Validators
3
+ class DuplicateIDs < Validator
4
+ def on_completion
5
+ if @duplicate_ids
6
+ @duplicate_ids.each do |id, nodes|
7
+ Origen.log.error "Test ID #{id} is defined more than once in flow #{flow.name}:"
8
+ nodes.each do |node|
9
+ Origen.log.error " #{node.source}"
10
+ end
11
+ end
12
+ true
13
+ end
14
+ end
15
+
16
+ def on_id(node)
17
+ @existing_ids ||= {}
18
+ id = node.value
19
+ if @existing_ids[id]
20
+ @duplicate_ids ||= {}
21
+ if @duplicate_ids[id]
22
+ @duplicate_ids[id] << node
23
+ else
24
+ @duplicate_ids[id] = [@existing_ids[id], node]
25
+ end
26
+ else
27
+ @existing_ids[id] = node
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,55 @@
1
+ module ATP
2
+ module Validators
3
+ class MissingIDs < Validator
4
+ def setup
5
+ @referenced_ids = {}
6
+ @present_ids ||= {}.with_indifferent_access
7
+ @referenced_early = {}.with_indifferent_access
8
+ end
9
+
10
+ def on_completion
11
+ failed = false
12
+ @referenced_ids.each do |id, nodes|
13
+ unless @present_ids[id]
14
+ Origen.log.error "Test ID #{id} is referenced in flow #{flow.name} in the following lines, but it is never defined:"
15
+ nodes.each do |node|
16
+ Origen.log.error " #{node.source}"
17
+ end
18
+ failed = true
19
+ @referenced_early.delete(id)
20
+ end
21
+ end
22
+ @referenced_early.each do |id, nodes|
23
+ Origen.log.error "Test ID #{id} is referenced in flow #{flow.name} in the following line(s):"
24
+ nodes.each do |node|
25
+ Origen.log.error " #{node.source}"
26
+ end
27
+ Origen.log.error 'but it was not defined until later:'
28
+ Origen.log.error " #{@present_ids[id].first.source}"
29
+ failed = true
30
+ end
31
+ failed
32
+ end
33
+
34
+ def on_id(node)
35
+ id = node.value
36
+ @present_ids[id] ||= []
37
+ @present_ids[id] << node
38
+ end
39
+
40
+ def on_test_executed(node)
41
+ ids = node.to_a[0]
42
+ [ids].flatten.each do |id|
43
+ @referenced_ids[id] ||= []
44
+ @referenced_ids[id] << node
45
+ unless @present_ids[id]
46
+ @referenced_early[id] ||= []
47
+ @referenced_early[id] << node
48
+ end
49
+ end
50
+ process_all(node)
51
+ end
52
+ alias_method :on_test_result, :on_test_executed
53
+ end
54
+ end
55
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: atp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen McGinty
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-01-20 00:00:00.000000000 Z
11
+ date: 2016-01-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: origen
@@ -78,12 +78,16 @@ files:
78
78
  - lib/atp/parser.rb
79
79
  - lib/atp/processor.rb
80
80
  - lib/atp/processors/condition.rb
81
+ - lib/atp/processors/condition_extractor.rb
81
82
  - lib/atp/processors/post_cleaner.rb
82
83
  - lib/atp/processors/pre_cleaner.rb
83
84
  - lib/atp/processors/relationship.rb
84
85
  - lib/atp/program.rb
85
86
  - lib/atp/runner.rb
87
+ - lib/atp/validator.rb
86
88
  - lib/atp/validators/condition.rb
89
+ - lib/atp/validators/duplicate_ids.rb
90
+ - lib/atp/validators/missing_ids.rb
87
91
  - lib/tasks/atp.rake
88
92
  - templates/web/archive.md.erb
89
93
  - templates/web/contact.md.erb