require 'time'
require 'sysinfo'
require 'digest/sha1'
require 'ostruct'
require 'yaml'
begin; require 'json'; rescue LoadError; end # json may not be installed
GYMNASIUM_HOME = File.join(Dir.pwd, '{tryouts,try}') ## also check try (for rye)
GYMNASIUM_GLOB = File.join(GYMNASIUM_HOME, '**', '*_tryouts.rb')
# = Tryouts
#
# This class has three purposes:
# * It represents the Tryouts object which is a group of Tryout objects.
# * The tryouts and dreams DSLs are executed within its namespace. In general the
# class methods are the handlers for the DSL syntax (some instance getter methods
# are modified to support DSL syntax by acting like setters when given arguments)
# * It stores all known instances of Tryouts objects in a class variable @@instances.
#
# ==== Are you ready to run some drills?
#
# May all your dreams come true!
#
class Tryouts
# = Exception
# A generic exception which all other Tryouts exceptions inherit from.
class Exception < RuntimeError; end
# = BadDreams
# Raised when there is a problem loading or parsing a Tryouts::Drill::Dream object
class BadDreams < Exception; end
VERSION = "0.7.0"
require 'tryouts/mixins'
require 'tryouts/tryout'
require 'tryouts/drill'
require 'tryouts/stats'
require 'tryouts/orderedhash'
HASH_TYPE = (RUBY_VERSION =~ /1.9/) ? ::Hash : Tryouts::OrderedHash
# An Array of +_tryouts.rb+ file paths that have been loaded.
@@loaded_files = []
# An Hash of Tryouts instances stored under the name of the Tryouts subclass.
@@instances = HASH_TYPE.new
# An instance of SysInfo
@@sysinfo = SysInfo.new
@@debug = false
@@verbose = 0
# This will be true if any error occurred during any of the drills or parsing.
@@failed = false
def self.debug?; @@debug; end
def self.enable_debug; @@debug = true; end
def self.disable_debug; @@debug = false; end
def self.verbose; @@verbose; end
def self.verbose=(v); @@verbose = (v == true) ? 1 : v; end
def self.failed?; @@failed; end
def self.failed=(v); @@failed = v; end
# Returns +@@instances+
def self.instances; @@instances; end
# Returns +@@sysinfo+
def self.sysinfo; @@sysinfo; end
# The name of this group of Tryout objects
attr_accessor :group
# A Symbol representing the default drill type. One of: :cli, :api
attr_accessor :dtype
# An Array of file paths which populated this instance of Tryouts
attr_accessor :paths
# An Array of Tryout objects
attr_accessor :tryouts
# A Symbol representing the command taking part in the tryouts. For @dtype :cli only.
attr_accessor :command
# A Symbol representing the name of the library taking part in the tryouts. For @dtype :api only.
attr_accessor :library
# An Array of exceptions that were raised during the tryouts that were not captured by a drill.
attr_reader :errors
def initialize(group=nil)
@group = group || "Default Group"
@tryouts = HASH_TYPE.new
@paths, @errors = [], []
@command = nil
end
# Populate this Tryouts from a block. The block should contain calls to
# the external DSL methods: tryout, command, library, group
def from_block(b, &inline)
instance_eval &b
end
# Execute Tryout#report for each Tryout in +@tryouts+
def report
successes = []
@tryouts.each_pair { |n,to| successes << to.report }
puts $/, "All your dreams came true" unless successes.member?(false)
end
# Execute Tryout#run for each Tryout in +@tryouts+
def run; @tryouts.each_pair { |n,to| to.run }; end
# Add a shell command to Rye::Cmd and save the command name
# in @@commands so it can be used as the default for drills
def command(name=nil, path=nil)
raise "command testing is temporarily disabled"
return @command if name.nil?
@command = name.to_sym
@dtype = :cli
Rye::Cmd.module_eval do
define_method(name) do |*args|
cmd(path || name, *args)
end
end
@command
end
# Calls Tryouts#command on the current instance of Tryouts
#
# NOTE: this is a standalone DSL-syntax method.
def self.command(*args)
@@instances.last.command(*args)
end
# Require +name+. If +path+ is supplied, it will "require path".
# * +name+ The name of the library in question (required). Stored as a Symbol to +@library+.
# * +path+ Add a path to the front of $LOAD_PATH (optional). Use this if you want to load
# a specific copy of the library. Otherwise, it loads from the system path.
def library(name=nil, path=nil)
return @library if name.nil?
@library = name.to_sym
@dtype = :api
$LOAD_PATH.unshift path unless path.nil?
begin
require @library.to_s
rescue SyntaxError, LoadError, Exception, TypeError,
RuntimeError, NoMethodError, NameError => ex
@errors << ex
Tryouts.failed = true
end
end
# Calls Tryouts#library on the current instance of Tryouts
#
# NOTE: this is a standalone DSL-syntax method.
def self.library(*args)
@@instances.last.library(*args)
end
def group(name=nil)
return @group if name.nil?
@group = name unless name.nil?
@group
end
# Raises a Tryouts::Exception. +group+ is not support in the standalone syntax
# because the group name is taken from the name of the class. See inherited.
#
# NOTE: this is a standalone DSL-syntax method.
def self.group(*args)
raise "Group is already set: #{@@instances.last.group}"
end
# Create a new Tryout object and add it to the list for this Tryouts class.
# * +name+ is the name of the Tryout
# * +type+ is the default drill type for the Tryout. One of: :cli, :api
# * +command+ when type is :cli, this is the name of the Rye::Box method that we're testing. Otherwise ignored.
# * +b+ is a block definition for the Tryout. See Tryout#from_block
#
# NOTE: This is a DSL-only method and is not intended for OO use.
def tryout(name, dtype=nil, command=nil, &block)
return if name.nil?
dtype ||= @dtype
command ||= @command if dtype == :cli
raise "No drill type specified for #{name}." if dtype.nil?
to = find_tryout(name, dtype)
if to.nil?
to = Tryouts::Tryout.new(name, dtype, command)
@tryouts[name] = to
end
# Process the rest of the DSL
begin
to.from_block block if block
rescue SyntaxError, LoadError, Exception, TypeError,
RuntimeError, NoMethodError, NameError => ex
@errors << ex
Tryouts.failed = true
end
to
end
# Calls Tryouts#tryout on the current instance of Tryouts
#
# NOTE: this is a standalone DSL-syntax method.
def self.tryout(*args, &block)
@@instances.last.tryout(*args, &block)
end
# Find matching Tryout objects by +name+ and filter by
# +dtype+ if specified. Returns a Tryout object or nil.
def find_tryout(name, dtype=nil)
by_name = @tryouts.values.select { |t| t.name == name }
by_name = by_name.select { |t| t.dtype == dtype } if dtype
by_name.first # by_name is an Array. We just want the Object.
end
# This method does nothing. It provides a quick way to disable a tryout.
#
# NOTE: This is a DSL-only method and is not intended for OO use.
def xtryout(*args, &block); end
# This method does nothing. It provides a quick way to disable a tryout.
#
# NOTE: this is a standalone DSL-syntax method.
def self.xtryout(*args, &block); end
# Returns +@tryouts+.
#
# Also acts as a stub for Tryouts#tryout in case someone
# specifies "tryouts 'name' do ..." in the DSL.
def tryouts(*args, &block)
return tryout(*args, &block) unless args.empty?
@tryouts
end
# An alias for Tryouts.tryout.
def self.tryouts(*args, &block)
tryout(args, &block)
end
# Parse a +_tryouts.rb+ file. See Tryouts::CLI::Run for an example.
#
# NOTE: this is an OO syntax method
def self.parse_file(fpath)
raise "No such file: #{fpath}" unless File.exists?(fpath)
file_content = File.read(fpath)
to = Tryouts.new
begin
to.instance_eval file_content, fpath
# After parsing the DSL, we'll know the group name.
# If a Tryouts object already exists for that group
# we'll use that instead and re-parse the DSL.
if @@instances.has_key? to.group
to = @@instances[to.group]
to.instance_eval file_content, fpath
end
to.paths << fpath
rescue SyntaxError, LoadError, Exception, TypeError,
RuntimeError, NoMethodError, NameError => ex
to.errors << ex
Tryouts.failed = true
end
@@instances[to.group] = to
to
end
# Run all Tryout objects in +@tryouts+
#
# NOTE: this is an OO syntax method
def self.run
@@instances.each_pair do |group, inst|
inst.tryouts.each_pair do |name,to|
to.run
to.report
STDOUT.flush
end
end
end
# Called when a new class inherits from Tryouts. This creates a new instance
# of Tryouts, sets group to the name of the new class, and adds the instance
# to +@@instances+.
#
# NOTE: this is a standalone DSL-syntax method.
def self.inherited(klass)
to = @@instances[ klass ]
to ||= Tryouts.new
to.paths << __FILE__
to.group = klass
@@instances[to.group] = to
end
##---
## Is this wacky syntax useful for anything?
## t2 :set .
## run = "poop"
## def self.t2(*args)
## OpenStruct.new
## end
##+++
end