tryouts 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,25 @@
1
+
2
+ class Hash
3
+
4
+ # A depth-first look to find the deepest point in the Hash.
5
+ # The top level Hash is counted in the total so the final
6
+ # number is the depth of its children + 1. An example:
7
+ #
8
+ # ahash = { :level1 => { :level2 => {} } }
9
+ # ahash.deepest_point # => 3
10
+ #
11
+ def deepest_point(h=self, steps=0)
12
+ if h.is_a?(Hash)
13
+ steps += 1
14
+ h.each_pair do |n,possible_h|
15
+ ret = deepest_point(possible_h, steps)
16
+ steps = ret if steps < ret
17
+ end
18
+ else
19
+ return 0
20
+ end
21
+ steps
22
+ end
23
+
24
+ end
25
+
@@ -0,0 +1,2 @@
1
+
2
+ require "tryouts/mixins/hash"
@@ -0,0 +1,171 @@
1
+
2
+ class Tryouts
3
+
4
+ # = Tryout
5
+ #
6
+ # A Tryout is a set of drills (each drill is a test).
7
+ #
8
+ class Tryout
9
+
10
+ # The name of this tryout
11
+ attr_reader :name
12
+
13
+ # A Hash of Dream objects for this Tryout. The keys are drill names.
14
+ attr_accessor :dreams
15
+
16
+ # An Array of Drill objects
17
+ attr_reader :drills
18
+
19
+ # A default value for Drill.dtype
20
+ attr_reader :dtype
21
+
22
+ # For drill type :cli, this is the name of the command to test. It
23
+ # should be a valid method available to a Rye::Box object.
24
+ # For drill type :api, this attribute is ignored.
25
+ attr_reader :command
26
+
27
+ # A block to executed one time before starting the drills
28
+ attr_reader :setup
29
+
30
+ # A block to executed one time before starting the drills
31
+ attr_reader :clean
32
+
33
+ @@valid_dtypes = [:cli, :api]
34
+
35
+ # All :api Drills are run within this context (not used for :cli).
36
+ # Each Drill is executed in a new instance of this class. That means
37
+ # instance variables are not carried through, but class variables are.
38
+ # The before and after blocks are also run in this context.
39
+ class DrillContext; end
40
+
41
+ def initialize(name, dtype, command=nil, *args)
42
+ raise "Must supply command for dtype :cli" if dtype == :cli && command.nil?
43
+ raise "#{dtype} is not a valid drill type" if !@@valid_dtypes.member?(dtype)
44
+ @name, @dtype, @command = name, dtype, command
45
+ @drills = []
46
+ @dreams = {}
47
+ end
48
+
49
+ ## --------------------------------------- EXTERNAL API -----
50
+
51
+ # Populate this Tryout from a block. The block should contain calls to
52
+ # the external DSL methods: dream, drill, xdrill
53
+ def from_block(b=nil, &inline)
54
+ runtime = b.nil? ? inline : b
55
+ instance_eval &runtime
56
+ end
57
+
58
+ # Execute all Drill objects
59
+ def run
60
+ update_drills! # Ensure all drills have all known dreams
61
+ DrillContext.class_eval &setup if setup.is_a?(Proc)
62
+ puts Tryouts::TRYOUT_MSG % @name
63
+ @drills.each do |drill|
64
+ drill.run(DrillContext.new) # Returns true or false
65
+ end
66
+ DrillContext.class_eval &clean if clean.is_a?(Proc)
67
+ end
68
+
69
+ # Prints error output. If there are no errors, it prints nothing.
70
+ def report
71
+ return true if success?
72
+ failed = @drills.select { |d| !d.success? }
73
+ failed.each_with_index do |drill,index|
74
+
75
+ puts '%sERROR %2d/%-2d in "%s"' % [$/, index+1, failed.size, drill.name]
76
+
77
+ if drill.dream
78
+ puts '%24s: %s (expected %s)' % ["response code", drill.reality.rcode, drill.dream.rcode]
79
+ puts '%24s: %s' % ["expected output", drill.dream.output.inspect]
80
+ puts '%24s: %s' % ["actual output", drill.reality.output.inspect]
81
+ if drill.reality.emsg || (drill.reality.emsg != drill.dream.emsg)
82
+ puts '%24s: %s' % ["expected error msg", drill.dream.emsg.inspect]
83
+ puts '%24s: %s' % ["actual error msg", drill.reality.emsg.inspect]
84
+ end
85
+ else
86
+ puts '%24s' % ["[nodream]"]
87
+ if drill.reality.rcode > 0
88
+ puts '%24s: %s' % ["rcode", drill.reality.rcode.inspect]
89
+ puts '%24s: %s' % ["msg", drill.reality.emsg.inspect]
90
+ end
91
+ end
92
+
93
+ if drill.reality.rcode > 0
94
+ puts '%24s: ' % ["backtrace"]
95
+ puts drill.reality.backtrace, $/
96
+ end
97
+ end
98
+ false
99
+ end
100
+
101
+ # Did every Tryout finish successfully?
102
+ def success?
103
+ return @success unless @success.nil?
104
+ # Returns true only when every Tryout result returns true
105
+ @success = !(@drills.collect { |r| r.success? }.member?(false))
106
+ end
107
+
108
+ # Add a Drill object to the list for this Tryout. If there is a dream
109
+ # defined with the same name as the Drill, that dream will be given to
110
+ # the Drill before its added to the list.
111
+ def add_drill(d)
112
+ d.add_dream @dreams[d.name] if !@dreams.nil? && @dreams.has_key?(d.name)
113
+ drills << d if d.is_a?(Tryouts::Drill)
114
+ d
115
+ end
116
+
117
+ # Goes through the list of Drill objects (@drills) and gives each
118
+ # one its associated Dream object (if available).
119
+ #
120
+ # This method is called before Tryout#run, but is only necessary in
121
+ # the case where dreams where loaded after the drills were defined.
122
+ def update_drills!
123
+ return if @dreams.nil?
124
+ @drills.each do |drill|
125
+ next unless @dreams.has_key?(drill.name)
126
+ drill.add_dream @dreams[drill.name]
127
+ end
128
+ end
129
+
130
+ ## --------------------------------------- EXTERNAL DSL -----
131
+
132
+ # A block to executed one time _before_ starting the drills
133
+ def setup(&block)
134
+ return @setup unless block
135
+ @setup = block
136
+ end
137
+
138
+ # A block to executed one time _after_ the drills
139
+ def clean(&block)
140
+ return @clean unless block
141
+ @clean = block
142
+ end
143
+
144
+ # +name+ of the Drill associated to this Dream
145
+ # +output+ A String or Array of expected output. A Dream object will be created using this value (optional)
146
+ # +definition+ is a block which will be run on an instance of Dream
147
+ #
148
+ # NOTE: This method is DSL-only. It's not intended to be used in OO syntax.
149
+ def dream(name, output=nil, format=nil, rcode=0, emsg=nil, &definition)
150
+ if output.nil?
151
+ dobj = Tryouts::Drill::Dream.from_block definition
152
+ else
153
+ dobj = Tryouts::Drill::Dream.new(output)
154
+ dobj.format, dobj.rcode, dobj.emsg = format, rcode, emsg
155
+ end
156
+ @dreams[name] = dobj
157
+ dobj
158
+ end
159
+
160
+ # Create and add a Drill object to the list for this Tryout
161
+ # +name+ is the name of the drill.
162
+ # +args+ is sent directly to the Drill class. The values are specific on the Sergeant.
163
+ def drill(name, *args, &definition)
164
+ args.unshift(@command) if @dtype == :cli
165
+ drill = Tryouts::Drill.new(name, @dtype, *args, &definition)
166
+ add_drill drill
167
+ end
168
+ def xdrill(*args, &b); end # ignore calls to xdrill
169
+
170
+
171
+ end; end
data/lib/tryouts.rb ADDED
@@ -0,0 +1,386 @@
1
+
2
+ require 'rubygems'
3
+ require 'ostruct'
4
+ require 'rye'
5
+ require 'yaml'
6
+ begin; require 'json'; rescue LoadError; end # json may not be installed
7
+
8
+ GYMNASIUM_HOME = File.join(Dir.pwd, 'tryouts')
9
+ GYMNASIUM_GLOB = File.join(GYMNASIUM_HOME, '**', '*_tryouts.rb')
10
+
11
+
12
+ # = Tryouts
13
+ #
14
+ # This class has three purposes:
15
+ # * It represents the Tryouts object which is a group of Tryout objects.
16
+ # * The tryouts and dreams DSLs are executed within its namespace. In general the
17
+ # class methods are the handlers for the DSL syntax (some instance getter methods
18
+ # are modified to support DSL syntax by acting like setters when given arguments)
19
+ # * It stores all known instances of Tryouts objects in a class variable @@instances.
20
+ #
21
+ # ==== Are you ready to run some drills?
22
+ #
23
+ # May all your dreams come true!
24
+ #
25
+ class Tryouts
26
+ # = Exception
27
+ # A generic exception which all other Tryouts exceptions inherit from.
28
+ class Exception < RuntimeError; end
29
+ # = BadDreams
30
+ # Raised when there is a problem loading or parsing a Tryouts::Drill::Dream object
31
+ class BadDreams < Exception; end
32
+
33
+ VERSION = "0.4.0"
34
+
35
+ require 'tryouts/mixins'
36
+ require 'tryouts/tryout'
37
+ require 'tryouts/drill'
38
+
39
+ require 'tryouts/orderedhash'
40
+ HASH_TYPE = (RUBY_VERSION =~ /1.9/) ? ::Hash : Tryouts::OrderedHash
41
+
42
+ TRYOUT_MSG = "\n %s "
43
+ DRILL_MSG = ' %30s: '
44
+ DRILL_ERR = ' %s: '
45
+
46
+ # An Array of +_tryouts.rb+ file paths that have been loaded.
47
+ @@loaded_files = []
48
+ # An Hash of Tryouts instances stored under the name of the Tryouts subclass.
49
+ @@instances = HASH_TYPE.new
50
+
51
+ # The name of this group of Tryout objects
52
+ attr_accessor :group
53
+ # A Symbol representing the default drill type. One of: :cli, :api
54
+ attr_accessor :dtype
55
+ # An Array of file paths which populated this instance of Tryouts
56
+ attr_accessor :paths
57
+ # A Hash of dreams for all tryouts in this class. The keys should
58
+ # match the names of each tryout. The values are hashes will drill
59
+ # names as keys and response
60
+ attr_accessor :dreams
61
+ # An Array of Tryout objects
62
+ attr_accessor :tryouts
63
+ # A Symbol representing the command taking part in the tryouts. For @dtype :cli only.
64
+ attr_accessor :command
65
+ # A Symbol representing the name of the library taking part in the tryouts. For @dtype :api only.
66
+ attr_accessor :library
67
+ # The name of the most recent dreams group (see self.dream)
68
+ attr_accessor :dream_pointer
69
+
70
+ # Returns +@@instances+
71
+ def self.instances; @@instances; end
72
+
73
+ def initialize(group=nil)
74
+ @group = group || "Default Group"
75
+ @tryouts = HASH_TYPE.new
76
+ @paths = []
77
+ @command = nil
78
+ @dreams = HASH_TYPE.new
79
+ @dream_pointer = nil
80
+ end
81
+
82
+ # Populate this Tryouts from a block. The block should contain calls to
83
+ # the external DSL methods: tryout, command, dreams
84
+ def from_block(b, &inline)
85
+ instance_eval &b
86
+ end
87
+
88
+ # Execute Tryout#report for each Tryout in +@tryouts+
89
+ def report
90
+ successes = []
91
+ @tryouts.each_pair { |n,to| successes << to.report }
92
+ puts $/, "All your dreams came true" unless successes.member?(false)
93
+ end
94
+
95
+ # Execute Tryout#run for each Tryout in +@tryouts+
96
+ def run; @tryouts.each_pair { |n,to| to.run }; end
97
+
98
+ # Add a shell command to Rye::Cmd and save the command name
99
+ # in @@commands so it can be used as the default for drills
100
+ def command(name=nil, path=nil)
101
+ return @command if name.nil?
102
+ @command = name.to_sym
103
+ @dtype = :cli
104
+ Rye::Cmd.module_eval do
105
+ define_method(name) do |*args|
106
+ cmd(path || name, *args)
107
+ end
108
+ end
109
+ @command
110
+ end
111
+ # Calls Tryouts#command on the current instance of Tryouts
112
+ #
113
+ # NOTE: this is a standalone DSL-syntax method.
114
+ def self.command(*args)
115
+ @@instances.last.command(*args)
116
+ end
117
+
118
+
119
+ # Require +name+. If +path+ is supplied, it will "require path".
120
+ # * +name+ The name of the library in question (required). Stored as a Symbol to +@library+.
121
+ # * +path+ Add a path to the front of $LOAD_PATH (optional). Use this if you want to load
122
+ # a specific copy of the library. Otherwise, it loads from the system path.
123
+ def library(name=nil, path=nil)
124
+ return @library if name.nil?
125
+ @library = name.to_sym
126
+ @dtype = :api
127
+ $LOAD_PATH.unshift path unless path.nil?
128
+ require @library.to_s
129
+ end
130
+ # Calls Tryouts#library on the current instance of Tryouts
131
+ #
132
+ # NOTE: this is a standalone DSL-syntax method.
133
+ def self.library(*args)
134
+ @@instances.last.library(*args)
135
+ end
136
+
137
+ def group(name=nil)
138
+ return @group if name.nil?
139
+ @group = name unless name.nil?
140
+ # Preload dreams if possible
141
+ dfile = self.class.find_dreams_file(GYMNASIUM_HOME, @group)
142
+ self.load_dreams_file(dfile) if dfile
143
+ @group
144
+ end
145
+ # Raises a Tryouts::Exception. +group+ is not support in the standalone syntax
146
+ # because the group name is taken from the name of the class. See inherited.
147
+ #
148
+ # NOTE: this is a standalone DSL-syntax method.
149
+ def self.group(*args)
150
+ raise "Group is already set: #{@@instances.last.group}"
151
+ end
152
+
153
+ # Create a new Tryout object and add it to the list for this Tryouts class.
154
+ # * +name+ is the name of the Tryout
155
+ # * +type+ is the default drill type for the Tryout. One of: :cli, :api
156
+ # * +command+ when type is :cli, this is the name of the Rye::Box method that we're testing. Otherwise ignored.
157
+ # * +b+ is a block definition for the Tryout. See Tryout#from_block
158
+ #
159
+ # NOTE: This is a DSL-only method and is not intended for OO use.
160
+ def tryout(name, dtype=nil, command=nil, &block)
161
+ return if name.nil?
162
+ dtype ||= @dtype
163
+ command ||= @command if dtype == :cli
164
+ to = find_tryout(name, dtype)
165
+ if to.nil?
166
+ to = Tryouts::Tryout.new(name, dtype, command)
167
+ @tryouts[name] = to
168
+ end
169
+ # Populate the dreams if they've already been loaded
170
+ to.dreams = @dreams[name] if @dreams.has_key?(name)
171
+ # Process the rest of the DSL
172
+ to.from_block block if block
173
+ to
174
+ end
175
+ # Calls Tryouts#tryout on the current instance of Tryouts
176
+ #
177
+ # NOTE: this is a standalone DSL-syntax method.
178
+ def self.tryout(*args, &block)
179
+ @@instances.last.tryout(*args, &block)
180
+ end
181
+
182
+ # Find matching Tryout objects by +name+ and filter by
183
+ # +dtype+ if specified. Returns a Tryout object or nil.
184
+ def find_tryout(name, dtype=nil)
185
+ by_name = @tryouts.values.select { |t| t.name == name }
186
+ by_name = by_name.select { |t| t.dtype == dtype } if dtype
187
+ by_name.first # by_name is an Array. We just want the Object.
188
+ end
189
+
190
+ # This method does nothing. It provides a quick way to disable a tryout.
191
+ #
192
+ # NOTE: This is a DSL-only method and is not intended for OO use.
193
+ def xtryout(*args, &block); end
194
+ # This method does nothing. It provides a quick way to disable a tryout.
195
+ #
196
+ # NOTE: this is a standalone DSL-syntax method.
197
+ def self.xtryout(*args, &block); end
198
+
199
+ # Load dreams from a file or directory or if a block is given
200
+ # it's processed
201
+ # Raises a Tryouts::BadDreams exception when something goes awry.
202
+ #
203
+ # This method is used in two ways:
204
+ # * In the dreams file DSL
205
+ # * As a getter method on a Tryouts object
206
+ def dreams(tryout_name=nil, &definition)
207
+ return @dreams unless tryout_name
208
+
209
+ #
210
+ # dreams "path/2/dreams"
211
+ # OR
212
+ # dreams "path/2/file_of_dreams.rb"
213
+ #
214
+ if File.exists?(tryout_name)
215
+ dfile = tryout_name
216
+ # If we're given a directory we'll build the filename using the class name
217
+ if File.directory?(tryout_name)
218
+ dfile = self.class.find_dreams_file(tryout_name, @group)
219
+ end
220
+ raise BadDreams, "Cannot find dreams file (#{tryout_name})" unless dfile
221
+ @dreams = load_dreams_file( dfile) || {}
222
+
223
+ #
224
+ # dreams "Tryout Name" do
225
+ # dream "drill name" ...
226
+ # end
227
+ #
228
+ elsif tryout_name.kind_of?(String) && definition
229
+ to = find_tryout(tryout_name, @dtype)
230
+
231
+ if to.nil?
232
+ @dream_pointer = tryout_name # Used in Tryouts.dream
233
+ @dreams[ @dream_pointer ] ||= {}
234
+ definition.call
235
+ else
236
+ to.from_block &definition
237
+ end
238
+ else
239
+ raise BadDreams, tryout_name
240
+ end
241
+ @dreams
242
+ end
243
+ # Without arguments, returns a Hash of all known dreams.
244
+ # With arguments, it calls Tryouts#dreams on the current instance of Tryouts.
245
+ #
246
+ # NOTE: this is a standalone DSL-syntax method.
247
+ def self.dreams(*args, &block)
248
+ if args.empty? && block.nil?
249
+ dreams = {}
250
+ @@instances.each_pair do |name,inst|
251
+ dreams[name] = inst.dreams
252
+ end
253
+ return dreams
254
+ else
255
+ # Call the Tryouts#dreams instance method
256
+ @@instances.last.dreams(*args, &block)
257
+ end
258
+ end
259
+
260
+ # +name+ of the Drill associated to this Dream
261
+ # +output+ A String or Array of expected output. A Dream object will be created using this value (optional)
262
+ # +definition+ is a block which will be run on an instance of Dream
263
+ #
264
+ # This method is different than Tryout#dream because this one stores
265
+ # dreams inside an instance variable of the current Tryouts object.
266
+ # This allows for the situation where the dreams block appears before
267
+ # the tryout block. See Tryouts#tryout
268
+ #
269
+ # NOTE: This method is DSL-only. It's not intended to be used in OO syntax.
270
+ def dream(name, output=nil, format=nil, rcode=0, emsg=nil, &definition)
271
+ to = find_tryout(@dream_pointer, @dtype)
272
+ if to.nil?
273
+ if output.nil?
274
+ dobj = Tryouts::Drill::Dream.from_block definition
275
+ else
276
+ dobj = Tryouts::Drill::Dream.new(output)
277
+ dobj.format, dobj.rcode, dobj.emsg = format, rcode, emsg
278
+ end
279
+ @dreams[@dream_pointer][name] = dobj
280
+ else
281
+ # Let the Tryout object process the dream DSL.
282
+ # We'll get here if the dream is placed after
283
+ # the drill with the same name in the same block.
284
+ to.dream name, output, format, rcode, emsg, &definition
285
+ end
286
+ end
287
+ # Calls Tryouts#dream on the current instance of Tryouts
288
+ #
289
+ # NOTE: this is a standalone DSL-syntax method.
290
+ def self.dream(*args, &block)
291
+ @@instances.last.dream(*args, &block)
292
+ end
293
+
294
+ # Populate @@dreams with the content of the file +dpath+.
295
+ #
296
+ # NOTE: this is an OO syntax method
297
+ def load_dreams_file(dpath)
298
+ type = File.extname dpath
299
+ if type == ".yaml" || type == ".yml"
300
+ @dreams = YAML.load_file dpath
301
+ elsif type == ".json" || type == ".js"
302
+ @dreams = JSON.load_file dpath
303
+ elsif type == ".rb"
304
+ @dreams = instance_eval File.read(dpath)
305
+ else
306
+ raise BadDreams, "Unknown kind of dream: #{dpath}"
307
+ end
308
+ @dreams
309
+ end
310
+
311
+ # Parse a +_tryouts.rb+ file. See Tryouts::CLI::Run for an example.
312
+ #
313
+ # NOTE: this is an OO syntax method
314
+ def self.parse_file(fpath)
315
+ raise "No such file: #{fpath}" unless File.exists?(fpath)
316
+ file_content = File.read(fpath)
317
+ to = Tryouts.new
318
+ to.instance_eval file_content, fpath
319
+ if @@instances.has_key? to.group
320
+ to = @@instances[to.group]
321
+ to.instance_eval file_content, fpath
322
+ end
323
+ to.paths << fpath
324
+ @@instances[to.group] = to
325
+ end
326
+
327
+ # Run all Tryout objects in +@tryouts+
328
+ #
329
+ # NOTE: this is an OO syntax method
330
+ def self.run
331
+ @@instances.each_pair do |group, inst|
332
+ inst.tryouts.each_pair do |name,to|
333
+ to.run
334
+ to.report
335
+ STDOUT.flush
336
+ end
337
+ end
338
+ end
339
+
340
+ # Called when a new class inherits from Tryouts. This creates a new instance
341
+ # of Tryouts, sets group to the name of the new class, and adds the instance
342
+ # to +@@instances+.
343
+ #
344
+ # NOTE: this is a standalone DSL-syntax method.
345
+ def self.inherited(klass)
346
+ to = @@instances[ klass ]
347
+ to ||= Tryouts.new
348
+ to.paths << __FILE__
349
+ to.group = klass
350
+ @@instances[to.group] = to
351
+ end
352
+
353
+
354
+ ##---
355
+ ## Is this wacky syntax useful for anything?
356
+ ## t2 :set .
357
+ ## run = "poop"
358
+ ## def self.t2(*args)
359
+ ## OpenStruct.new
360
+ ## end
361
+ ##+++
362
+
363
+ # Find a dreams file in the directory +dir+ based on the current group name.
364
+ # The expected filename format is: groupname_dreams.ext where "groupname" is
365
+ # the lowercase name of the Tryouts group (spaces removed) and "ext" is one
366
+ # of: yaml, js, json, rb.
367
+ #
368
+ # e.g.
369
+ # Tryouts.find_dreams_file "dirpath" # => dirpath/tryouts_dreams.rb
370
+ #
371
+ def self.find_dreams_file(dir, group=nil)
372
+ dpath = nil
373
+ group ||= @@instances.last.group
374
+ group = group.to_s.downcase.tr(' ', '')
375
+ [:rb, :yaml].each do |ext|
376
+ tmp = File.join(dir, "#{group}_dreams.#{ext}")
377
+ if File.exists?(tmp)
378
+ dpath = tmp
379
+ break
380
+ end
381
+ end
382
+ dpath
383
+ end
384
+
385
+
386
+ end
@@ -0,0 +1,19 @@
1
+
2
+ dreams "Common Usage" do
3
+ dream "No args" do
4
+ output inline(%Q{
5
+ Date: 2009-02-16
6
+ Owners: greg, rupaul, telly, prince kinko
7
+ Players: d-bam, alberta, birds, condor man
8
+ })
9
+ end
10
+ dream "YAML Output" do
11
+ format :yaml
12
+ output ({
13
+ "Date" => "2009-02-16",
14
+ "Players" => ["d-bam", "alberta", "birds", "condor man"],
15
+ "Owners" => ["greg", "rupaul", "telly", "prince kinko"]
16
+ })
17
+ end
18
+ end
19
+
@@ -0,0 +1,10 @@
1
+ common usage:
2
+ yaml output:
3
+ :format: :yaml
4
+ :rcode: 0
5
+ no args:
6
+ :rcode: 0
7
+ :output:
8
+ - " Date: 2009-02-16\n"
9
+ - " Players: d-bam, alberta, birds, condor man\n"
10
+ - " Coaches: greg|rupaul|telly|prince kinko\n"
@@ -0,0 +1,26 @@
1
+
2
+ TRYOUTS_HOME = File.expand_path(File.join(File.dirname(__FILE__), ".."))
3
+ MOCKOUT_PATH = File.join(TRYOUTS_HOME, "bin", "mockout")
4
+
5
+ group "mockout cli"
6
+ command :mockout, MOCKOUT_PATH
7
+
8
+ tryout "Common Usage" do
9
+ drill "No Command"
10
+ drill "No args", :info
11
+ drill "YAML Output", :f, :yaml, :info
12
+ drill "JSON Output", :f, :json, :info
13
+ end
14
+
15
+ tryout "inline dream that passes", :cli, :mockout do
16
+ output = ["we expect mockout to", "echo these lines back"]
17
+
18
+ # $ bin/mockout sergeant -e "we expect mockout to" "echo these lines back"
19
+ drill "echo arguments", :info, :e, output[0], output[1]
20
+ dream "echo arguments", output
21
+ end
22
+
23
+ tryout "inline dream that fails", :cli, :mockout do
24
+ dream "echo arguments", "The dream does"
25
+ drill "echo arguments", :info, :e, "not match reality"
26
+ end