tryouts 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.txt +6 -0
- data/LICENSE.txt +19 -0
- data/README.rdoc +122 -0
- data/Rakefile +80 -0
- data/bin/mockout +49 -0
- data/bin/tryouts +39 -0
- data/lib/tryouts/cli/run.rb +94 -0
- data/lib/tryouts/cli.rb +15 -0
- data/lib/tryouts/drill/response.rb +105 -0
- data/lib/tryouts/drill/sergeant/cli.rb +49 -0
- data/lib/tryouts/drill.rb +98 -0
- data/lib/tryouts/mixins/hash.rb +25 -0
- data/lib/tryouts/mixins.rb +2 -0
- data/lib/tryouts/tryout.rb +171 -0
- data/lib/tryouts.rb +386 -0
- data/tryouts/mockoutcli_dreams.rb +19 -0
- data/tryouts/mockoutcli_dreams.yaml +10 -0
- data/tryouts/mockoutcli_tryouts.rb +26 -0
- data/tryouts.gemspec +75 -0
- metadata +117 -0
data/CHANGES.txt
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2009 Solutious Inc, Delano Mandelbaum
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
= Tryouts - v0.5 ALPHA
|
2
|
+
|
3
|
+
Tryouts is a high-level testing library for your command-line applications and Ruby codes.
|
4
|
+
|
5
|
+
*WORK IN PROGRESS*
|
6
|
+
|
7
|
+
2009-05-24 - A BBQ started nearby as I was writing this documentation. It's incomplete and I'm sorry about that but I can tell you that this BBQ smells really, really good.
|
8
|
+
|
9
|
+
|
10
|
+
== Terminology
|
11
|
+
|
12
|
+
Tryouts is a bit different than other testing libraries. Test definitions are organized in a similar way as Shoulda tests (although the keywords in the syntax are different).
|
13
|
+
|
14
|
+
* Tryout: a set of drills (like basketball tryouts)
|
15
|
+
* Drill: a test.
|
16
|
+
* Dream: the expected outcome of a drill.
|
17
|
+
|
18
|
+
|
19
|
+
== Testing a command-line application (a CLI)
|
20
|
+
|
21
|
+
Tryouts tests command-line applications by comparing expected output with the actual output. Let's say we have an executable called mockout and we want to test the following commands:
|
22
|
+
|
23
|
+
$ bin/executable
|
24
|
+
$ bin/executable -f yaml
|
25
|
+
|
26
|
+
The tryout definition would look like this:
|
27
|
+
|
28
|
+
command :executable, "path/2/executable"
|
29
|
+
|
30
|
+
tryout "Common Usage", :cli do
|
31
|
+
drill "No Command"
|
32
|
+
drill "YAML Output", :f, 'yaml'
|
33
|
+
end
|
34
|
+
|
35
|
+
And the expected output would be defined like this:
|
36
|
+
|
37
|
+
dream "No Command" do
|
38
|
+
output inline(%Q{
|
39
|
+
Date: 2009-02-16
|
40
|
+
Players: d-bam, alberta, birds, condor man
|
41
|
+
Owners: greg, rupaul, telly, prince kinko
|
42
|
+
})
|
43
|
+
end
|
44
|
+
dream "YAML Output" do
|
45
|
+
format :yaml
|
46
|
+
output ({
|
47
|
+
"Date" => "2009-02-16",
|
48
|
+
"Players" => ["d-bam", "alberta", "birds", "condor man"],
|
49
|
+
"Owners" => ["greg", "rupaul", "telly", "prince kinko"]
|
50
|
+
})
|
51
|
+
end
|
52
|
+
|
53
|
+
== Testing Ruby codes (an API)
|
54
|
+
|
55
|
+
Tryouts employs the same approach for testing Ruby codes. The return value of the drill block is compared to the expectation defined by the dream. Here is an example of including dreams inside the tryout definition.
|
56
|
+
|
57
|
+
library :caesars, LIBRARY_PATH
|
58
|
+
|
59
|
+
tryout "Common Usage", :api do
|
60
|
+
dream "Some Maths", 3
|
61
|
+
drill "Some Maths" do
|
62
|
+
12 / 4
|
63
|
+
end
|
64
|
+
|
65
|
+
dream "A Block", Proc, :class # Test the class type instead of the value
|
66
|
+
drill "A Block" do
|
67
|
+
Proc.new do
|
68
|
+
:anything
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
== ALPHA Notice
|
75
|
+
|
76
|
+
This library is very new (est. 2009-05-19) and has not been vetted by the scrutiny of time. In particular you can expect:
|
77
|
+
|
78
|
+
* Ugly/awkward output from the tryouts executable. I wrote the core functionality that I needed to start writing tryouts for my other projects. I haven't spent very much time on reporting yet. However, this will change!
|
79
|
+
* The test definition syntax may change in future releases.
|
80
|
+
* Unexpected errors.
|
81
|
+
|
82
|
+
== 3 Ways to define tryouts
|
83
|
+
There are three ways to define an instance of this class:
|
84
|
+
* In +_tryouts.rb+ files using the DSL syntax. One file per Tryouts object.
|
85
|
+
* See: http://github.com/delano/tryouts/blob/master/tryouts/mockoutcli_tryouts.rb
|
86
|
+
* In standalone ruby files using a hybrid DSL/OO syntax. Supports multiple
|
87
|
+
Tryouts objects per file.
|
88
|
+
* See: http://github.com/delano/tryouts/blob/master/tryouts/standalone_test.rb
|
89
|
+
* With regular object-oriented syntax.
|
90
|
+
* See: http://tryouts.rubyforge.org/
|
91
|
+
|
92
|
+
== On Threads
|
93
|
+
|
94
|
+
Tryouts does some funky stuff to make it simple to write tests. This "funky
|
95
|
+
stuff" means that this library is *not thread-safe at definition-time*. However, once
|
96
|
+
all tryouts files are parsed (or in OO-syntax, once all objects are created), this
|
97
|
+
class should be *thread-safe at drill-time*.
|
98
|
+
|
99
|
+
== More Info
|
100
|
+
|
101
|
+
* Check out the sourcecodes[http://github.com/delano/tryouts]
|
102
|
+
* Read the rdocs[http://tryouts.rubyforge.org/]
|
103
|
+
* About Solutious[http://solutious.com/about/]
|
104
|
+
|
105
|
+
== Thanks
|
106
|
+
|
107
|
+
* Everyone at Utrecht.rb
|
108
|
+
|
109
|
+
== Credits
|
110
|
+
|
111
|
+
* Delano (@solutious.com)
|
112
|
+
|
113
|
+
== Related Projects
|
114
|
+
|
115
|
+
* Context[http://github.com/jeremymcanally/context/tree/master]
|
116
|
+
* Testy[http://github.com/ahoward/testy/tree/master]
|
117
|
+
* Expectations[http://expectations.rubyforge.org/]
|
118
|
+
* Zebra[http://github.com/giraffesoft/zebra/tree/master]
|
119
|
+
|
120
|
+
== License
|
121
|
+
|
122
|
+
See: LICENSE.txt
|
data/Rakefile
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/clean'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
require 'hanna/rdoctask'
|
5
|
+
require 'fileutils'
|
6
|
+
include FileUtils
|
7
|
+
|
8
|
+
task :default => :package
|
9
|
+
|
10
|
+
# CONFIG =============================================================
|
11
|
+
|
12
|
+
# Change the following according to your needs
|
13
|
+
README = "README.rdoc"
|
14
|
+
CHANGES = "CHANGES.txt"
|
15
|
+
LICENSE = "LICENSE.txt"
|
16
|
+
|
17
|
+
# Files and directories to be deleted when you run "rake clean"
|
18
|
+
CLEAN.include [ 'pkg', '*.gem', '.config', 'doc']
|
19
|
+
|
20
|
+
# Virginia assumes your project and gemspec have the same name
|
21
|
+
name = (Dir.glob('*.gemspec') || ['tryouts']).first.split('.').first
|
22
|
+
load "#{name}.gemspec"
|
23
|
+
version = @spec.version
|
24
|
+
|
25
|
+
# That's it! The following defaults should allow you to get started
|
26
|
+
# on other things.
|
27
|
+
|
28
|
+
|
29
|
+
# TESTS/SPECS =========================================================
|
30
|
+
|
31
|
+
|
32
|
+
|
33
|
+
# INSTALL =============================================================
|
34
|
+
|
35
|
+
Rake::GemPackageTask.new(@spec) do |p|
|
36
|
+
p.need_tar = true if RUBY_PLATFORM !~ /mswin/
|
37
|
+
end
|
38
|
+
|
39
|
+
task :release => [ :rdoc, :package ]
|
40
|
+
task :install => [ :rdoc, :package ] do
|
41
|
+
sh %{sudo gem install pkg/#{name}-#{version}.gem}
|
42
|
+
end
|
43
|
+
task :uninstall => [ :clean ] do
|
44
|
+
sh %{sudo gem uninstall #{name}}
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
# RUBYFORGE RELEASE / PUBLISH TASKS ==================================
|
49
|
+
|
50
|
+
if @spec.rubyforge_project
|
51
|
+
desc 'Publish website to rubyforge'
|
52
|
+
task 'publish:rdoc' => 'doc/index.html' do
|
53
|
+
sh "scp -rp doc/* rubyforge.org:/var/www/gforge-projects/#{name}/"
|
54
|
+
end
|
55
|
+
|
56
|
+
desc 'Public release to rubyforge'
|
57
|
+
task 'publish:gem' => [:package] do |t|
|
58
|
+
sh <<-end
|
59
|
+
rubyforge add_release -o Any -a #{CHANGES} -f -n #{README} #{name} #{name} #{@spec.version} pkg/#{name}-#{@spec.version}.gem &&
|
60
|
+
rubyforge add_file -o Any -a #{CHANGES} -f -n #{README} #{name} #{name} #{@spec.version} pkg/#{name}-#{@spec.version}.tgz
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
|
67
|
+
# RUBY DOCS TASK ==================================
|
68
|
+
|
69
|
+
Rake::RDocTask.new do |t|
|
70
|
+
t.rdoc_dir = 'doc'
|
71
|
+
t.title = @spec.summary
|
72
|
+
t.options << '--line-numbers' << '-A cattr_accessor=object'
|
73
|
+
t.options << '--charset' << 'utf-8'
|
74
|
+
t.rdoc_files.include(LICENSE)
|
75
|
+
t.rdoc_files.include(README)
|
76
|
+
t.rdoc_files.include(CHANGES)
|
77
|
+
t.rdoc_files.include('bin/tryouts')
|
78
|
+
t.rdoc_files.include('lib/**/*.rb')
|
79
|
+
end
|
80
|
+
|
data/bin/mockout
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
# = Mockout
|
4
|
+
#
|
5
|
+
# This mock is used to generate test output for Tryouts itself.
|
6
|
+
# It is otherwise uninteresting!
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'rubygems'
|
10
|
+
require 'drydock'
|
11
|
+
require 'yaml'
|
12
|
+
|
13
|
+
begin; require 'json'; rescue LoadError; end # json may not be installed
|
14
|
+
|
15
|
+
module DrillCLI
|
16
|
+
extend Drydock
|
17
|
+
|
18
|
+
global :f, :format, String, "One of: json, yaml, string (default)"
|
19
|
+
|
20
|
+
|
21
|
+
default :info
|
22
|
+
debug :on
|
23
|
+
|
24
|
+
data = {
|
25
|
+
"Date" => "2009-02-16",
|
26
|
+
"Owners" => ["greg", "rupaul", "telly", "prince kinko"],
|
27
|
+
"Players" => ["d-bam", "alberta", "birds", "condor man"]
|
28
|
+
}
|
29
|
+
|
30
|
+
option :e, :echo, "Echo the arguments"
|
31
|
+
command :info do |obj|
|
32
|
+
format = obj.global.format.nil? ? nil : "to_#{obj.global.format}"
|
33
|
+
format = nil if obj.global.format == 'string'
|
34
|
+
if obj.option.echo
|
35
|
+
puts obj.argv
|
36
|
+
else
|
37
|
+
if format.nil?
|
38
|
+
data.keys.sort.each do |n|
|
39
|
+
val = data[n]
|
40
|
+
val = val.join(', ') if val.is_a?(Array)
|
41
|
+
puts "%9s %44s" % ["#{n}:", val]
|
42
|
+
end
|
43
|
+
else
|
44
|
+
puts data.send(format)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
data/bin/tryouts
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
#!/usr/local/bin/ruby
|
2
|
+
|
3
|
+
TRYOUTS_HOME = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
4
|
+
|
5
|
+
local_libs = %w{tryouts net-scp amazon-ec2 aws-s3 caesars drydock rye storable sysinfo annoy}
|
6
|
+
local_libs.each { |dir| $:.unshift File.join(TRYOUTS_HOME, '..', dir, 'lib') }
|
7
|
+
|
8
|
+
require 'drydock'
|
9
|
+
require 'tryouts'
|
10
|
+
require 'tryouts/cli'
|
11
|
+
|
12
|
+
# = TryoutsCLI
|
13
|
+
#
|
14
|
+
# This is the Drydock definition for the bin/tryouts executable
|
15
|
+
module TryoutsCLI
|
16
|
+
extend Drydock
|
17
|
+
|
18
|
+
debug :on
|
19
|
+
default :run, :with_args
|
20
|
+
|
21
|
+
global :q, :quiet, "Decrease output"
|
22
|
+
global :v, :verbose, "Increase output" do
|
23
|
+
@verbose ||= 0
|
24
|
+
@verbose += 1
|
25
|
+
end
|
26
|
+
|
27
|
+
about "Run tryouts from current working directory"
|
28
|
+
argv :files
|
29
|
+
command :run => Tryouts::CLI::Run
|
30
|
+
|
31
|
+
about "Show dreams available from the current working directory"
|
32
|
+
argv :files
|
33
|
+
command :dreams => Tryouts::CLI::Run
|
34
|
+
|
35
|
+
about "Show tryouts available from the current working directory"
|
36
|
+
argv :files
|
37
|
+
command :list => Tryouts::CLI::Run
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
|
2
|
+
class Tryouts; module CLI
|
3
|
+
|
4
|
+
# = Run
|
5
|
+
#
|
6
|
+
# The logic bin/tryouts uses for running tryouts.
|
7
|
+
class Run < Drydock::Command
|
8
|
+
|
9
|
+
def init
|
10
|
+
@tryouts_globs = [GYMNASIUM_GLOB, File.join(Dir.pwd, '*_tryouts.rb')]
|
11
|
+
end
|
12
|
+
|
13
|
+
def dreams
|
14
|
+
load_available_tryouts_files
|
15
|
+
if @global.verbose > 0
|
16
|
+
puts Tryouts.dreams.to_yaml
|
17
|
+
else
|
18
|
+
Tryouts.dreams.each_pair do |n,dreams|
|
19
|
+
puts n
|
20
|
+
dreams.each_pair do |n, dream|
|
21
|
+
puts " " << n
|
22
|
+
dream.each_pair do |n, drill|
|
23
|
+
puts " " << n
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def run
|
31
|
+
if @global.verbose > 0
|
32
|
+
puts "RUBY #{RUBY_VERSION} - #{RUBY_PLATFORM}"
|
33
|
+
end
|
34
|
+
|
35
|
+
load_available_tryouts_files
|
36
|
+
|
37
|
+
successes = []
|
38
|
+
Tryouts.instances.each_pair do |group,tryouts_inst|
|
39
|
+
puts '', '-'*60 unless @global.quiet
|
40
|
+
puts group
|
41
|
+
puts " #{tryouts_inst.paths.join("\n ")}" if @global.verbose > 0
|
42
|
+
tryouts_inst.tryouts.each_pair do |name,to|
|
43
|
+
to.run
|
44
|
+
to.report
|
45
|
+
STDOUT.flush
|
46
|
+
successes << to.success?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
unless successes.member?(false)
|
50
|
+
puts $/, "All your dreams came true" unless @global.quiet
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def list
|
55
|
+
load_available_tryouts_files
|
56
|
+
##if @global.verbose > 2
|
57
|
+
## puts Tryouts.instances.to_yaml # BUG: Raises "can't dump anonymous class Class"
|
58
|
+
##else
|
59
|
+
Tryouts.instances.each_pair do |n,tryouts_inst|
|
60
|
+
puts n
|
61
|
+
if @global.verbose > 0
|
62
|
+
puts " #{tryouts_inst.paths.join("\n ")}"
|
63
|
+
end
|
64
|
+
tryouts_inst.tryouts.each_pair do |t2,tryout|
|
65
|
+
puts " " << tryout.name
|
66
|
+
tryout.drills.each do |drill|
|
67
|
+
puts " " << drill.name
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
##end
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
def load_available_tryouts_files
|
76
|
+
@tryouts_files = []
|
77
|
+
# If file paths were given, check those only.
|
78
|
+
unless @argv.empty?
|
79
|
+
@argv.each do |file|
|
80
|
+
file = File.join(file, '**', '*_tryouts.rb') if File.directory?(file)
|
81
|
+
@tryouts_files += Dir.glob file
|
82
|
+
end
|
83
|
+
# Otherwise check the default globs
|
84
|
+
else
|
85
|
+
@tryouts_globs.each do |glob|
|
86
|
+
@tryouts_files += Dir.glob glob
|
87
|
+
end
|
88
|
+
end
|
89
|
+
@tryouts_files.uniq! # Don't load the same file twice
|
90
|
+
@tryouts_files.each { |file| Tryouts.parse_file file }
|
91
|
+
puts @tryouts_files if @global.verbose > 0
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end; end
|
data/lib/tryouts/cli.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
|
2
|
+
class Tryouts::Drill
|
3
|
+
# = Response
|
4
|
+
#
|
5
|
+
# A generic base class for Dream and Reality
|
6
|
+
#
|
7
|
+
class Response
|
8
|
+
attr_accessor :output, :format, :rcode, :emsg, :backtrace
|
9
|
+
def initialize(output=nil, format=nil, rcode=0)
|
10
|
+
@output, @format, @rcode = output, format, (rcode || 0)
|
11
|
+
@output = nil if @output.nil?
|
12
|
+
normalize!
|
13
|
+
end
|
14
|
+
|
15
|
+
def ==(other)
|
16
|
+
return false if other.nil?
|
17
|
+
@rcode == other.rcode &&
|
18
|
+
@emsg == other.emsg &&
|
19
|
+
compare_output(other)
|
20
|
+
end
|
21
|
+
|
22
|
+
def rcode(val=nil); @rcode = val unless val.nil?; normalize!; @rcode; end
|
23
|
+
def output(val=nil); @output = val unless val.nil?; normalize!; @output; end
|
24
|
+
def emsg(val=nil); @emsg = val unless val.nil?; normalize!; @emsg; end
|
25
|
+
def format(val=nil); @format = val unless val.nil?; normalize!; @format; end
|
26
|
+
|
27
|
+
def output=(val); @output = val; normalize!; @output; end
|
28
|
+
def rcode=(val); @rcode = val; normalize!; @rcode; end
|
29
|
+
def format=(val); @format = val; normalize!; @format; end
|
30
|
+
def emsg=(val); @emsg = val; normalize!; @emsg; end
|
31
|
+
|
32
|
+
# Enforce the following restrictions on the data fields:
|
33
|
+
# * +rcode+ is an Integer
|
34
|
+
# * +format+ is a Symbol
|
35
|
+
# This method is called automatically any time a field is updated.
|
36
|
+
def normalize!
|
37
|
+
@rcode = @rcode.to_i if @rcode.is_a?(String)
|
38
|
+
@format = @format.to_sym if @format.is_a?(String)
|
39
|
+
end
|
40
|
+
def compare_output(other)
|
41
|
+
#
|
42
|
+
# The dream is always on the left (Does your dream match reality?)
|
43
|
+
# This check is important so we can support both:
|
44
|
+
# @dream == @reality
|
45
|
+
# AND
|
46
|
+
# @reality == @dream
|
47
|
+
#
|
48
|
+
if self.is_a? Tryouts::Drill::Dream
|
49
|
+
dream, reality = self, other
|
50
|
+
elsif self.is_a? Tryouts::Drill::Reality
|
51
|
+
dream, reality = other, self
|
52
|
+
else
|
53
|
+
# If self isn't a Dream or a Reality, then we have a problem
|
54
|
+
return false
|
55
|
+
end
|
56
|
+
|
57
|
+
# The matching statement will be the return value.
|
58
|
+
if dream.format.nil?
|
59
|
+
dream.output == reality.output
|
60
|
+
elsif reality.respond_to? dream.format
|
61
|
+
reality.output.send(dream.format) == dream.output
|
62
|
+
else
|
63
|
+
false
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
# = Dream
|
72
|
+
#
|
73
|
+
# Contains the expected response of a Drill
|
74
|
+
#
|
75
|
+
class Dream < Tryouts::Drill::Response
|
76
|
+
|
77
|
+
def self.from_block(definition)
|
78
|
+
d = Tryouts::Drill::Dream.new
|
79
|
+
d.from_block definition
|
80
|
+
d
|
81
|
+
end
|
82
|
+
|
83
|
+
def from_block(definition)
|
84
|
+
instance_eval &definition
|
85
|
+
self.normalize!
|
86
|
+
self
|
87
|
+
end
|
88
|
+
|
89
|
+
# Takes a String +val+ and splits the lines into an Array. Each line
|
90
|
+
# has
|
91
|
+
def inline(val=nil)
|
92
|
+
lines = (val.split($/) || [])
|
93
|
+
lines.shift if lines.first.strip == ""
|
94
|
+
lines.pop if lines.last.strip == ""
|
95
|
+
lines
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# = Reality
|
100
|
+
#
|
101
|
+
# Contains the actual response of a Drill
|
102
|
+
#
|
103
|
+
class Reality < Tryouts::Drill::Response; end
|
104
|
+
|
105
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
class Tryouts; class Drill; module Sergeant
|
4
|
+
|
5
|
+
# = CLI
|
6
|
+
#
|
7
|
+
# The sergeant responsible for running command-line interface drills.
|
8
|
+
#
|
9
|
+
class CLI
|
10
|
+
|
11
|
+
attr_reader :rbox
|
12
|
+
|
13
|
+
# An Array of arguments to be sent to +rbox.send(*rbox_args)+
|
14
|
+
attr_accessor :rbox_args
|
15
|
+
|
16
|
+
def initialize(*args)
|
17
|
+
@rbox_args = args
|
18
|
+
@rbox = Rye::Box.new
|
19
|
+
end
|
20
|
+
|
21
|
+
# NOTE: Context is ignored for the CLI Sergeant.
|
22
|
+
def run(block, context=nil, &inline)
|
23
|
+
# A Proc object takes precedence over an inline block.
|
24
|
+
runtime = (block.nil? ? inline : block)
|
25
|
+
response = Tryouts::Drill::Reality.new
|
26
|
+
begin
|
27
|
+
if runtime.nil?
|
28
|
+
ret = @rbox.send *rbox_args
|
29
|
+
else
|
30
|
+
ret = @rbox.instance_eval &runtime
|
31
|
+
end
|
32
|
+
response.rcode = ret.exit_code
|
33
|
+
response.output = ret.stdout.size == 1 ? ret.stdout.first : Array.new(ret.stdout) # Cast the Rye::Rap object
|
34
|
+
response.emsg = ret.stderr unless ret.stderr.empty?
|
35
|
+
rescue Rye::CommandNotFound => ex
|
36
|
+
response.rcode = -2
|
37
|
+
response.emsg = "[#{@rbox.host}] Command not found: #{ex.message}"
|
38
|
+
response.backtrace = ex.backtrace
|
39
|
+
rescue Rye::CommandError => ex
|
40
|
+
response.rcode = ex.exit_code
|
41
|
+
response.output = ex.stdout
|
42
|
+
response.emsg = ex.stderr
|
43
|
+
end
|
44
|
+
response
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end; end; end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
class Tryouts
|
4
|
+
|
5
|
+
# = Drill
|
6
|
+
#
|
7
|
+
# This class represents a drill. A drill is single test.
|
8
|
+
#
|
9
|
+
class Drill
|
10
|
+
|
11
|
+
require 'tryouts/drill/response'
|
12
|
+
require 'tryouts/drill/sergeant/cli'
|
13
|
+
require 'tryouts/drill/sergeant/api'
|
14
|
+
|
15
|
+
class NoSergeant < Tryouts::Exception; end
|
16
|
+
|
17
|
+
# A symbol specifying the drill type. One of: :cli, :api
|
18
|
+
attr_reader :dtype
|
19
|
+
# The name of the drill. This should match the name used in the dreams file.
|
20
|
+
attr_reader :name
|
21
|
+
# A Proc object which contains the drill logic.
|
22
|
+
attr_reader :drill
|
23
|
+
|
24
|
+
# A Sergeant object which executes the drill
|
25
|
+
attr_reader :sergeant
|
26
|
+
# A Dream object (the expected output of the test)
|
27
|
+
attr_reader :dream
|
28
|
+
# A Reality object (the actual output of the test)
|
29
|
+
attr_reader :reality
|
30
|
+
|
31
|
+
def initialize(name, dtype, *drill_args, &drill)
|
32
|
+
@name, @dtype, @drill = name, dtype, drill
|
33
|
+
@sergeant = hire_sergeant *drill_args
|
34
|
+
# For CLI drills, a block takes precedence over inline args.
|
35
|
+
# A block will contain multiple shell commands (see Rye::Box#batch)
|
36
|
+
drill_args = [] if dtype == :cli && drill.is_a?(Proc)
|
37
|
+
@reality = Tryouts::Drill::Reality.new
|
38
|
+
end
|
39
|
+
|
40
|
+
def hire_sergeant(*drill_args)
|
41
|
+
if @dtype == :cli
|
42
|
+
Tryouts::Drill::Sergeant::CLI.new(*drill_args)
|
43
|
+
elsif @dtype == :api
|
44
|
+
Tryouts::Drill::Sergeant::API.new(drill_args.first)
|
45
|
+
else
|
46
|
+
raise NoSergeant, "What is #{@dtype}?"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def run(context=nil)
|
51
|
+
return false if @dream.nil?
|
52
|
+
begin
|
53
|
+
print Tryouts::DRILL_MSG % @name
|
54
|
+
@reality = @sergeant.run @drill, context
|
55
|
+
process_reality
|
56
|
+
rescue => ex
|
57
|
+
@reality.rcode = -2
|
58
|
+
@reality.emsg, @reality.backtrace = ex.message, ex.backtrace
|
59
|
+
end
|
60
|
+
note = @dream ? discrepency.join(', ') : 'nodream'
|
61
|
+
puts self.success? ? "PASS" : "FAIL (#{note})"
|
62
|
+
self.success?
|
63
|
+
end
|
64
|
+
|
65
|
+
def success?
|
66
|
+
return false if @dream.nil? || @reality.nil?
|
67
|
+
@dream == @reality
|
68
|
+
end
|
69
|
+
|
70
|
+
def discrepency
|
71
|
+
diffs = []
|
72
|
+
if @dream
|
73
|
+
diffs << "rcode" if @dream.rcode != @reality.rcode
|
74
|
+
diffs << "output" if !@dream.compare_output(@reality)
|
75
|
+
diffs << "emsg" if @dream.emsg != @reality.emsg
|
76
|
+
end
|
77
|
+
diffs
|
78
|
+
end
|
79
|
+
|
80
|
+
def add_dream(d)
|
81
|
+
@dream = d if d.is_a?(Tryouts::Drill::Dream)
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
# Use the :format provided in the dream to convert the output from reality
|
86
|
+
def process_reality
|
87
|
+
@reality.normalize!
|
88
|
+
return unless @dream && @dream.format
|
89
|
+
if @dream.format == :to_yaml
|
90
|
+
@reality.output = YAML.load(@reality.output.join("\n"))
|
91
|
+
elsif @dream.format == :to_json
|
92
|
+
@reality.output = JSON.load(@reality.output.join("\n"))
|
93
|
+
end
|
94
|
+
|
95
|
+
#p [:process, @name, @dream.format, @reality.output]
|
96
|
+
end
|
97
|
+
|
98
|
+
end; end
|