bahuvrihi-tap 0.10.6 → 0.10.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/bin/rap +13 -5
  2. data/lib/tap.rb +0 -4
  3. data/lib/tap/app.rb +10 -138
  4. data/lib/tap/constants.rb +1 -1
  5. data/lib/tap/declarations.rb +201 -0
  6. data/lib/tap/env.rb +10 -2
  7. data/lib/tap/exe.rb +2 -2
  8. data/lib/tap/generator/generators/root/templates/Rakefile +1 -0
  9. data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +1 -1
  10. data/lib/tap/spec.rb +38 -63
  11. data/lib/tap/spec/adapter.rb +8 -31
  12. data/lib/tap/spec/inheritable_class_test_root.rb +9 -0
  13. data/lib/tap/support/audit.rb +0 -2
  14. data/lib/tap/support/combinator.rb +83 -31
  15. data/lib/tap/support/configurable_class.rb +7 -7
  16. data/lib/tap/support/{dependable.rb → dependencies.rb} +9 -7
  17. data/lib/tap/support/executable.rb +226 -19
  18. data/lib/tap/support/gems/rake.rb +6 -3
  19. data/lib/tap/support/join.rb +87 -0
  20. data/lib/tap/support/joins.rb +13 -0
  21. data/lib/tap/support/joins/fork.rb +18 -0
  22. data/lib/tap/support/joins/merge.rb +20 -0
  23. data/lib/tap/support/joins/sequence.rb +21 -0
  24. data/lib/tap/support/joins/switch.rb +23 -0
  25. data/lib/tap/support/joins/sync_merge.rb +57 -0
  26. data/lib/tap/support/lazydoc/document.rb +10 -0
  27. data/lib/tap/support/node.rb +58 -0
  28. data/lib/tap/support/parser.rb +379 -0
  29. data/lib/tap/support/schema.rb +350 -0
  30. data/lib/tap/support/tdoc.rb +5 -0
  31. data/lib/tap/support/validation.rb +2 -0
  32. data/lib/tap/task.rb +26 -61
  33. data/lib/tap/test.rb +9 -82
  34. data/lib/tap/test/assertions.rb +38 -0
  35. data/lib/tap/test/extensions.rb +78 -0
  36. data/lib/tap/test/{file_methods.rb → file_test.rb} +64 -37
  37. data/lib/tap/test/{file_methods_class.rb → file_test_class.rb} +2 -2
  38. data/lib/tap/test/regexp_escape.rb +87 -0
  39. data/lib/tap/test/script_test.rb +46 -0
  40. data/lib/tap/test/script_tester.rb +109 -0
  41. data/lib/tap/test/{subset_methods.rb → subset_test.rb} +9 -9
  42. data/lib/tap/test/{subset_methods_class.rb → subset_test_class.rb} +22 -14
  43. data/lib/tap/test/{tap_methods.rb → tap_test.rb} +43 -6
  44. data/lib/tap/test/utils.rb +6 -3
  45. metadata +27 -24
  46. data/lib/tap/parser.rb +0 -619
  47. data/lib/tap/spec/file_methods.rb +0 -16
  48. data/lib/tap/spec/file_methods_class.rb +0 -13
  49. data/lib/tap/spec/subset_methods.rb +0 -14
  50. data/lib/tap/support/batchable.rb +0 -47
  51. data/lib/tap/support/batchable_class.rb +0 -107
  52. data/lib/tap/support/declarations.rb +0 -131
  53. data/lib/tap/support/lazydoc/declaration.rb +0 -20
  54. data/lib/tap/support/parsers/base.rb +0 -81
  55. data/lib/tap/support/parsers/server.rb +0 -113
  56. data/lib/tap/test/script_methods.rb +0 -75
  57. data/lib/tap/test/script_methods/regexp_escape.rb +0 -94
  58. data/lib/tap/test/script_methods/script_test.rb +0 -109
  59. data/lib/tap/workflow.rb +0 -161
@@ -0,0 +1,20 @@
1
+ module Tap
2
+ module Support
3
+ module Joins
4
+
5
+ class Merge < ReverseJoin
6
+ def join(target, sources)
7
+ sources.each do |source|
8
+ # merging can use the existing audit trails... each distinct
9
+ # input is getting sent to one place (the target)
10
+ complete(source) do |_result|
11
+ yield(_result) if block_given?
12
+ enq(target, _result)
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,21 @@
1
+ module Tap
2
+ module Support
3
+ module Joins
4
+
5
+ class Sequence < Join
6
+ def join(source, targets)
7
+ current_task = source
8
+ targets.each do |next_task|
9
+ # simply pass results from one task to the next.
10
+ complete(current_task) do |_result|
11
+ yield(_result) if block_given?
12
+ enq(next_task, _result)
13
+ end
14
+ current_task = next_task
15
+ end
16
+ end
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,23 @@
1
+ module Tap
2
+ module Support
3
+ module Joins
4
+
5
+ class Switch < Join
6
+ def join(source, targets)
7
+ complete(source) do |_result|
8
+ if index = yield(_result)
9
+ unless target = targets[index]
10
+ raise "no switch target for index: #{index}"
11
+ end
12
+
13
+ enq(target, _result)
14
+ else
15
+ source.app.aggregator.store(_result)
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,57 @@
1
+ module Tap
2
+ module Support
3
+ module Joins
4
+
5
+ class SyncMerge < ReverseJoin
6
+ def join(target, sources)
7
+
8
+ # a hash of (source, index) pairs where index is the
9
+ # index of the source in a combination
10
+ indicies = {}
11
+
12
+ # a hash of (source, combinations) pairs where combinations
13
+ # are combination arrays that the source participates in.
14
+ # note that in unbatched mode, some sources may not
15
+ # participate in any combinations.
16
+ combinations = {}
17
+
18
+ sets = sources.collect {|source| unbatched ? [source] : source.batch }
19
+ Support::Combinator.new(*sets).each do |combo|
20
+ combination = Array.new(combo.length, nil)
21
+
22
+ combo.each do |source|
23
+ indicies[source] ||= combo.index(source)
24
+ (combinations[source] ||= []) << combination
25
+ end
26
+ end
27
+
28
+ sources.each_with_index do |source, index|
29
+ complete(source) do |_result|
30
+ src = _result._current_source
31
+
32
+ source_index = indicies[src]
33
+ (combinations[src] ||= []).each do |combination|
34
+ if combination[source_index] != nil
35
+ raise "sync_merge collision... already got a result for #{src}"
36
+ end
37
+
38
+ combination[source_index] = _result
39
+ unless combination.include?(nil)
40
+ # merge the source audits
41
+ _merge_result = Support::Audit.merge(*combination)
42
+
43
+ yield(_merge_result) if block_given?
44
+ enq(target, _merge_result)
45
+
46
+ # reset the group array
47
+ combination.collect! {|i| nil }
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ end
56
+ end
57
+ end
@@ -62,6 +62,16 @@ module Tap
62
62
  def [](const_name)
63
63
  const_attrs[const_name] ||= {}
64
64
  end
65
+
66
+ # Returns an array of the const_names in self with at
67
+ # least one attribute.
68
+ def const_names
69
+ names = []
70
+ const_attrs.each_pair do |const_name, attrs|
71
+ names << const_name unless attrs.empty?
72
+ end
73
+ names
74
+ end
65
75
 
66
76
  # Register the specified line number to self. Returns a
67
77
  # comment_class instance corresponding to the line.
@@ -0,0 +1,58 @@
1
+ module Tap
2
+ module Support
3
+
4
+ # Represents a task node in a Schema.
5
+ class Node
6
+
7
+ # An array of arguments used to instantiate
8
+ # the node, and to specify arguments enqued
9
+ # to the instance (when the node is directly
10
+ # enqued to a round... see input)
11
+ attr_accessor :argv
12
+
13
+ # The input or source for the node. Inputs
14
+ # may be a Join, nil, or an Integer (indicating
15
+ # the node should be enqued to a round, with
16
+ # inputs as specified in argv).
17
+ attr_accessor :input
18
+
19
+ # The output for the node. Output may be a
20
+ # a Join or nil.
21
+ attr_accessor :output
22
+
23
+ def initialize(argv=[], input=nil, output=nil)
24
+ @argv = argv
25
+ @input = input
26
+ @output = output
27
+ end
28
+
29
+ # Resets the source and join to nil.
30
+ def globalize
31
+ self.input = nil
32
+ self.output = nil
33
+ end
34
+
35
+ # True if the input and output are nil.
36
+ def global?
37
+ input == nil && output == nil
38
+ end
39
+
40
+ # Returns the round for self; a round is indicated
41
+ # by an integer input. If input is anything but
42
+ # an integer, round returns nil.
43
+ def round
44
+ input.kind_of?(Integer) ? input : nil
45
+ end
46
+
47
+ # Alias for input=
48
+ def round=(input)
49
+ self.input = input
50
+ end
51
+
52
+ def inspect
53
+ "#<#{self.class}:#{object_id} argv=[#{argv.join(' ')}] input=#{input.inspect} output=#{output.inspect}>"
54
+ end
55
+
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,379 @@
1
+ require 'tap/support/schema'
2
+ autoload(:Shellwords, 'shellwords')
3
+
4
+ module Tap
5
+ module Support
6
+
7
+ # ==== Round Assignment
8
+ # Tasks can be defined and set to a round using the following:
9
+ #
10
+ # break assigns task(s) to round
11
+ # -- next 0
12
+ # --+ next 1
13
+ # --++ next 2
14
+ # --+2 next 2
15
+ # --+2[1,2,3] 1,2,3 2
16
+ #
17
+ # Here all task (except c) are parsed into round 0, then the
18
+ # final argument reassigns e to round 3.
19
+ #
20
+ # schema = Parser.new("a -- b --+ c -- d -- e --+3[4]").schema
21
+ # schema.rounds(true) # => [[0,1,3],[2], nil, [4]]
22
+ #
23
+ # ==== Workflow Assignment
24
+ # All simple workflow patterns except switch can be specified within
25
+ # the parse syntax (switch is the exception because there is no good
26
+ # way to define a block from an array).
27
+ #
28
+ # break pattern source(s) target(s)
29
+ # --: sequence last next
30
+ # --[] fork last next
31
+ # --{} merge next last
32
+ # --() sync_merge next last
33
+ #
34
+ # example meaning
35
+ # --1:2 1.sequence(2)
36
+ # --1:2:3 1.sequence(2,3)
37
+ # --:2: last.sequence(2,next)
38
+ # --[] last.fork(next)
39
+ # --1{2,3,4} 1.merge(2,3,4)
40
+ # --(2,3,4) last.sync_merge(2,3,4)
41
+ #
42
+ # Note how all of the bracketed styles behave similarly; they are
43
+ # parsed with essentially the same code, only reversing the source
44
+ # and target in the case of merges.
45
+ #
46
+ # Here a and b are sequenced inline. Task c is assigned to no
47
+ # workflow until the final argument which sequenced b and c.
48
+ #
49
+ # schema = Parser.new("a --: b -- c --1:2i").schema
50
+ # schema.argvs # => [["a"], ["b"], ["c"], []]
51
+ # schema.joins(true) # => [[:sequence,0,[1],{}], [:sequence,1,[2],{:iterate => true}]]
52
+ #
53
+ # ==== Globals
54
+ # Global instances of task (used, for example, by dependencies) may
55
+ # be assigned in the parse syntax as well. The break for a global
56
+ # is '--*'.
57
+ #
58
+ # schema = Parser.new("a -- b --* global_name --config for --global").schema
59
+ # schema.globals(true) # => [2]
60
+ #
61
+ # ==== Escapes and End Flags
62
+ # Breaks can be escaped by enclosing them in '-.' and '.-' delimiters;
63
+ # any number of arguments may be enclosed within the escape. After the
64
+ # end delimiter, breaks are active once again.
65
+ #
66
+ # schema = Parser.new("a -- b -- c").schema
67
+ # schema.argvs # => [["a"], ["b"], ["c"]]
68
+ #
69
+ # schema = Parser.new("a -. -- b .- -- c").schema
70
+ # schema.argvs # => [["a", "--", "b"], ["c"]]
71
+ #
72
+ # Parsing continues until the end of argv, or a an end flag '---' is
73
+ # reached. The end flag may also be escaped.
74
+ #
75
+ # schema = Parser.new("a -- b --- c").schema
76
+ # schema.argvs # => [["a"], ["b"]]
77
+ #
78
+ class Parser
79
+
80
+ # A set of parsing routines used internally by Tap::Support::Parser,
81
+ # modularized for ease of testing, and potential re-use. These methods
82
+ # require that <tt>current_index</tt> and <tt>previous_index</tt> be
83
+ # implemented in the including class.
84
+ module Utils
85
+ module_function
86
+
87
+ # Defines a break regexp that matches a bracketed-pairs
88
+ # break. The left and right brackets are specified as
89
+ # inputs. After a match:
90
+ #
91
+ # $1:: The source string after the break.
92
+ # (ex: '[]' => '', '1[]' => '1')
93
+ # $2:: The target string.
94
+ # (ex: '[]' => '', '1[1,2,3]' => '1,2,3')
95
+ # $3:: The modifier string.
96
+ # (ex: '[]i' => 'i', '1[1,2,3]is' => 'is')
97
+ #
98
+ def bracket_regexp(l, r)
99
+ /\A(\d*)#{Regexp.escape(l)}([\d,]*)#{Regexp.escape(r)}([A-z]*)\z/
100
+ end
101
+
102
+ # The escape begin argument
103
+ ESCAPE_BEGIN = "-."
104
+
105
+ # The escape end argument
106
+ ESCAPE_END = ".-"
107
+
108
+ # The parser end flag
109
+ END_FLAG = "---"
110
+
111
+ # Matches any breaking arg (ex: '--', '--+', '--1:2')
112
+ # After the match:
113
+ #
114
+ # $1:: The string after the break
115
+ # (ex: '--' => '', '--++' => '++', '--1(2,3)' => '1(2,3)')
116
+ #
117
+ BREAK = /\A--(\z|[\+\d\:\*\[\{\(].*\z)/
118
+
119
+ # Matches an execution-round break. After the match:
120
+ #
121
+ # $2:: The round string, or nil.
122
+ # (ex: '' => nil, '++' => '++', '+1' => '+1')
123
+ # $5:: The target string, or nil.
124
+ # (ex: '+' => nil, '+[1,2,3]' => '1,2,3')
125
+ #
126
+ ROUND = /\A((\+(\d*|\+*))(\[([\d,]*)\])?)?\z/
127
+
128
+ # Matches a sequence break. After the match:
129
+ #
130
+ # $1:: The sequence string after the break.
131
+ # (ex: ':' => ':', '1:2' => '1:2', '1:' => '1:', ':2' => ':2')
132
+ # $3:: The modifier string.
133
+ # (ex: ':i' => 'i', '1:2is' => 'is')
134
+ #
135
+ SEQUENCE = /\A(\d*(:\d*)+)([A-z]*)\z/
136
+
137
+ # Matches an instance break. After the match:
138
+ #
139
+ # $1:: The index string after the break.
140
+ # (ex: '*' => '', '*1' => '1')
141
+ #
142
+ INSTANCE = /\A\*(\d*)\z/
143
+
144
+ # A break regexp using "[]"
145
+ FORK = bracket_regexp("[", "]")
146
+
147
+ # A break regexp using "{}"
148
+ MERGE = bracket_regexp("{", "}")
149
+
150
+ # A break regexp using "()"
151
+ SYNC_MERGE = bracket_regexp("(", ")")
152
+
153
+ # Parses an indicies str along commas, and collects the indicies
154
+ # as integers. Ex:
155
+ #
156
+ # parse_indicies('') # => []
157
+ # parse_indicies('1') # => [1]
158
+ # parse_indicies('1,2,3') # => [1,2,3]
159
+ #
160
+ def parse_indicies(str, regexp=/,+/)
161
+ indicies = []
162
+ str.split(regexp).each do |n|
163
+ indicies << n.to_i unless n.empty?
164
+ end
165
+ indicies
166
+ end
167
+
168
+ # Parses the match of a ROUND regexp into a round index
169
+ # and an array of task indicies that should be added to the
170
+ # round. The inputs correspond to $2 and $5 for the match.
171
+ #
172
+ # If $2 is nil, a round index of zero is assumed; if $5 is
173
+ # nil or empty, then indicies of [:current_index] are assumed.
174
+ #
175
+ # parse_round("+", "") # => [1, [:current_index]]
176
+ # parse_round("+2", "1,2,3") # => [2, [1,2,3]]
177
+ # parse_round(nil, nil) # => [0, [:current_index]]
178
+ #
179
+ def parse_round(two, five)
180
+ index = case two
181
+ when nil then 0
182
+ when /\d/ then two[1, two.length-1].to_i
183
+ else two.length
184
+ end
185
+ [index, five.to_s.empty? ? [current_index] : parse_indicies(five)]
186
+ end
187
+
188
+ # Parses the match of a SEQUENCE regexp into an [indicies, options]
189
+ # array. The inputs corresponds to $1 and $3 for the match. The
190
+ # previous and current index are assumed if $1 starts and/or ends
191
+ # with a semi-colon.
192
+ #
193
+ # parse_sequence("1:2:3", '') # => [[1,2,3], {}]
194
+ # parse_sequence(":1:2:", '') # => [[:previous_index,1,2,:current_index], {}]
195
+ #
196
+ def parse_sequence(one, three)
197
+ seq = parse_indicies(one, /:+/)
198
+ seq.unshift previous_index if one[0] == ?:
199
+ seq << current_index if one[-1] == ?:
200
+ [seq, parse_options(three)]
201
+ end
202
+
203
+ # Parses the match of an INSTANCE regexp into an index.
204
+ # The input corresponds to $1 for the match. The current
205
+ # index is assumed if $1 is empty.
206
+ #
207
+ # parse_instance("1") # => 1
208
+ # parse_instance("") # => :current_index
209
+ #
210
+ def parse_instance(one)
211
+ one.empty? ? current_index : one.to_i
212
+ end
213
+
214
+ # Parses the match of an bracket_regexp into a [source_index,
215
+ # target_indicies, options] array. The inputs corresponds to $1,
216
+ # $2, and $3 for the match. The previous and current index are
217
+ # assumed if $1 and/or $2 is empty.
218
+ #
219
+ # parse_bracket("1", "2,3", "") # => [1, [2,3], {}]
220
+ # parse_bracket("", "", "") # => [:previous_index, [:current_index], {}]
221
+ # parse_bracket("1", "", "") # => [1, [:current_index], {}]
222
+ # parse_bracket("", "2,3", "") # => [:previous_index, [2,3], {}]
223
+ #
224
+ def parse_bracket(one, two, three)
225
+ targets = parse_indicies(two)
226
+ targets << current_index if targets.empty?
227
+ [one.empty? ? previous_index : one.to_i, targets, parse_options(three)]
228
+ end
229
+
230
+ # Parses an options string into a hash. The input corresponds
231
+ # to $3 in a SEQUENCE or bracket_regexp match. Raises an error
232
+ # if the options string contains unknown options.
233
+ #
234
+ # parse_options("") # => {}
235
+ # parse_options("is") # => {:iterate => true, :stack => true}
236
+ #
237
+ def parse_options(three)
238
+ options = {}
239
+ 0.upto(three.length - 1) do |char_index|
240
+ char = three[char_index, 1]
241
+ unless index = Join::SHORT_FLAGS.index(char)
242
+ raise "unknown option in: #{three} (#{char})"
243
+ end
244
+
245
+ options[Join::FLAGS[index]] = true
246
+ end
247
+ options
248
+ end
249
+ end
250
+
251
+ include Utils
252
+
253
+ # The schema into which nodes are parsed
254
+ attr_reader :schema
255
+
256
+ def initialize(argv=[])
257
+ @current_index = 0
258
+ @schema = Schema.new
259
+ parse(argv)
260
+ end
261
+
262
+ # Iterates through the argv splitting out task and workflow definitions.
263
+ # Task definitions are split out (with configurations) along round and/or
264
+ # workflow break lines. Rounds and workflows are dynamically parsed;
265
+ # tasks may be reassigned to different rounds or workflows by later
266
+ # arguments.
267
+ #
268
+ # Parse is non-destructive to argv. If a string argv is provided, parse
269
+ # splits it into an array using Shellwords.
270
+ #
271
+ def parse(argv)
272
+ parse!(argv.kind_of?(String) ? argv : argv.dup)
273
+ end
274
+
275
+ # Same as parse, but removes parsed args from argv.
276
+ def parse!(argv)
277
+ argv = Shellwords.shellwords(argv) if argv.kind_of?(String)
278
+ argv.unshift('--')
279
+
280
+ escape = false
281
+ current_argv = schema[current_index].argv
282
+
283
+ while !argv.empty?
284
+ arg = argv.shift
285
+
286
+ # if escaping, add escaped arguments
287
+ # until an escape-end argument
288
+ if escape
289
+ if arg == ESCAPE_END
290
+ escape = false
291
+ else
292
+ current_argv << arg
293
+ end
294
+
295
+ next
296
+ end
297
+
298
+ case arg
299
+ when ESCAPE_BEGIN
300
+ # begin escaping if indicated
301
+ escape = true
302
+
303
+ when END_FLAG
304
+ # break on an end-flag
305
+ break
306
+
307
+ when BREAK
308
+ # a breaking argument was reached:
309
+ # unless the current argv is empty,
310
+ # append and start a new definition
311
+ unless current_argv.empty?
312
+ self.current_index += 1
313
+ current_argv = schema[current_index].argv
314
+ end
315
+
316
+ # parse the break string for any
317
+ # schema modifications
318
+ parse_break($1)
319
+
320
+ else
321
+ # add all other non-breaking args to
322
+ # the current argv; this includes
323
+ # both inputs and configurations
324
+ current_argv << arg
325
+
326
+ end
327
+ end
328
+
329
+ schema
330
+ end
331
+
332
+ def load(argv)
333
+ argv.each do |arg|
334
+ case arg
335
+ when Array
336
+ schema.nodes << arg
337
+ self.current_index += 1
338
+ else
339
+ parse_break(arg)
340
+ end
341
+ end
342
+
343
+ schema
344
+ end
345
+
346
+ protected
347
+
348
+ # The index of the node currently being parsed.
349
+ attr_accessor :current_index
350
+
351
+ # Returns current_index-1, or raises an error if current_index < 1.
352
+ def previous_index
353
+ raise ArgumentError, 'there is no previous index' if current_index < 1
354
+ current_index - 1
355
+ end
356
+
357
+ # determines the type of break and modifies self appropriately
358
+ def parse_break(arg) # :nodoc:
359
+ case arg
360
+ when ROUND
361
+ round, indicies = parse_round($2, $5)
362
+ indicies.each {|index| schema[index].round = round }
363
+
364
+ when SEQUENCE
365
+ indicies, options = parse_sequence($1, $3)
366
+ while indicies.length > 1
367
+ schema.set(Joins::Sequence, indicies.shift, indicies[0], options)
368
+ end
369
+
370
+ when INSTANCE then schema[parse_instance($1)].globalize
371
+ when FORK then schema.set(Joins::Fork, *parse_bracket($1, $2, $3))
372
+ when MERGE then schema.set(Joins::Merge, *parse_bracket($1, $2, $3))
373
+ when SYNC_MERGE then schema.set(Joins::SyncMerge, *parse_bracket($1, $2, $3))
374
+ else raise ArgumentError, "invalid break argument: #{arg}"
375
+ end
376
+ end
377
+ end
378
+ end
379
+ end