Skip to content

Commit

Permalink
Added Tryouts::Drill::Sergeant::Benchmark and New dream format: :proc
Browse files Browse the repository at this point in the history
  • Loading branch information
delano committed Jun 25, 2009
1 parent 7e3c37e commit bb96132
Show file tree
Hide file tree
Showing 6 changed files with 240 additions and 72 deletions.
8 changes: 8 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
TRYOUTS, CHANGES


#### 0.6.4 (2009-06-25) ###############################

* ADDED: Tryouts::Drill::Sergeant::Benchmark
* ADDED: new dream format: :proc
dream lambda { |x| x.real < 0.1 }, :proc
drill("array sort!") { @@array.dup.sort! }


#### 0.6.3 (2009-06-25) ###############################

NOTE: command testing (:cli) is still disabled.
Expand Down
76 changes: 71 additions & 5 deletions lib/tryouts/drill.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ class Drill
require 'tryouts/drill/response'
require 'tryouts/drill/sergeant/cli'
require 'tryouts/drill/sergeant/api'
require 'tryouts/drill/sergeant/ben'
require 'tryouts/drill/sergeant/benchmark'
require 'tryouts/drill/sergeant/rbenchmark'

class NoSergeant < Tryouts::Exception; end
class UnknownFormat < Tryouts::Exception; end
Expand All @@ -31,7 +32,7 @@ class UnknownFormat < Tryouts::Exception; end
# A Reality object (the actual output of the test)
attr_reader :reality

@@valid_dtypes = [:cli, :api, :bm, :ben]
@@valid_dtypes = [:cli, :api, :benchmark]

def initialize(name, dtype, *args, &drill)
@name, @dtype, @drill, @skip = name, dtype, drill, false
Expand All @@ -43,14 +44,19 @@ def initialize(name, dtype, *args, &drill)
default_output = drill.nil? ? args.shift : nil
@sergeant = Tryouts::Drill::Sergeant::API.new default_output
@dreams << Tryouts::Drill::Dream.new(*args) unless args.empty?
when :bm, :ben
@sergeant = Tryouts::Drill::Sergeant::Ben.new
@dreams << Tryouts::Drill::Dream.new(true)
when :benchmark
default_output, format, reps = *args
@sergeant = Tryouts::Drill::Sergeant::Benchmark.new reps || 1
@dreams << Tryouts::Drill::Dream.new(Float, :class)
unless default_output.nil?
@dreams << Tryouts::Drill::Dream.new(default_output, format)
end
when :skip
@skip = true
else
raise NoSergeant, "Weird drill sergeant: #{@dtype}"
end
@clr = :red
# For CLI drills, a block takes precedence over inline args.
# A block will contain multiple shell commands (see Rye::Box#batch)
drill_args = [] if dtype == :cli && drill.is_a?(Proc)
Expand Down Expand Up @@ -78,6 +84,65 @@ def run(context=nil)
self.success?
end

def flag
if success?
"PASS".color(@clr).bright
else
note = @dreams.empty? ? '[nodream]' : ''
"FAIL #{note}".color(@clr).bright
end
end

def info
out = StringIO.new
if Tryouts.verbose > 1
if @dreams.empty?
out.puts '%6s%s'.color(@clr) % ['', @reality.output.inspect]
else
@dreams.each do |dream|
if dream != @reality
out.puts '%6s%s'.color(:red) % ['', @reality.output.inspect]
else
out.puts '%6s%s'.color(:green) % ["", dream.test_to_string(@reality)]
end
end
end
elsif Tryouts.verbose > 0
out.puts '%6s%s'.color(@clr) % ['', @reality.output.inspect]
end
out.rewind
out.read
end

def report
return if skip?
out = StringIO.new

@dreams.each do |dream|
next if dream == reality #? :normal : :red
out.puts '%12s: %s'.color(@clr) % ["failed", dream.test_to_string(@reality)]
out.puts '%12s: %s' % ["returned", @reality.comparison_value(dream).inspect]
out.puts '%12s: %s' % ["expected", dream.comparison_value.inspect]
out.puts
end

@reality.stash.each_pair do |n,v|
out.puts '%14s: %s' % [n,v.inspect]
end

unless @reality.error.nil?
out.puts '%14s: %s' % [@reality.etype, @reality.error.to_s.split($/).join($/ + ' '*16)]
end
unless @reality.trace.nil?
trace = Tryouts.verbose > 1 ? @reality.trace : [@reality.trace.first]
out.puts '%14s %s' % ['', trace.join($/ + ' '*16)]
out.puts
end

out.rewind
out.read
end

def success?
return false if @dreams.empty? && @reality.output != true
begin
Expand All @@ -86,6 +151,7 @@ def success?
puts ex.message, ex.backtrace if Tryouts.debug?
return false
end
@clr = :green
true
end

Expand Down
12 changes: 12 additions & 0 deletions lib/tryouts/drill/response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ def Response.compare(dream, reality)
when :match
reality.output.respond_to?(:match) &&
!reality.output.match(dream.output).nil?
when :proc
test = dream.output
test.is_a?(Proc) &&
test.arity > 0 ? test.call(reality.output) : test.call
when :gt
reality.output > dream.output
when :gte
Expand Down Expand Up @@ -66,6 +70,9 @@ def Response.compare_string(dream, reality)

begin
case dream.format
when :proc
test = dream.output
test.arity > 0 ? "Proc.call(reality) == true" : "Proc.call == true"
when :exception
"#{reality.etype} == #{dream.output}"
when :match
Expand Down Expand Up @@ -139,6 +146,11 @@ def test_to_string(reality)
def comparison_value
return @ret unless @ret.nil?
@ret = case @format
when :gt, :gte, :lt, :lte, :ne
op = {:gt=>'>',:gte=>'>=', :lt=>'<', :lte => '<=', :ne => '!='}.find { |i| i[0] == @format }
"#{op[1]} #{@output}"
when :proc
true
when :respond_to?, :is_a?, :kind_of?
true
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,20 @@

class Tryouts; class Drill; module Sergeant

# = API
# = Benchmark
#
# The sergeant responsible for running Ruby code (API) drills.
# The sergeant responsible for running benchmarks
#
class Ben
class Benchmark
require 'benchmark'

attr_reader :output

# +opts+ is a Hash with the following optional keys:
# * +reps+ Number of times to execute the block
#
# * +:output+ specify a return value. This will be
# used if no block is specified for the drill.
def initialize(output=nil)
@output = output
def initialize(reps=1)
@reps = reps
p [:reps, reps]
end

def run(block, context, &inline)
Expand All @@ -30,9 +29,7 @@ def run(block, context, &inline)
else
begin

Benchmark.bmbm &runtime

response.output = true
response.output = ::Benchmark.realtime &runtime

rescue => e
puts e.message, e.backtrace if Tryouts.verbose > 2
Expand Down
130 changes: 130 additions & 0 deletions lib/tryouts/drill/sergeant/rbenchmark.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@

require 'benchmark'


class Tryouts; class Drill; module Sergeant

# = RBenchmark
#
# This is an implementation of Better-Benchmark:
# http://github.com/Pistos/better-benchmark/
#
# NOTE: It's a work in progress and currently not functioning
#
module RBenchmark

VERSION = '0.7.0'

class ComparisonPartial
def initialize( block, options )
@block1 = block
@options = options
end

def with( &block2 )
times1 = []
times2 = []

(1..@options[ :iterations ]).each do |iteration|
if @options[ :verbose ]
$stdout.print "."; $stdout.flush
end

times1 << ::Benchmark.realtime do
@options[ :inner_iterations ].times do |i|
@block1.call( iteration )
end
end
times2 << ::Benchmark.realtime do
@options[ :inner_iterations ].times do |i|
block2.call( iteration )
end
end
end

r = RSRuby.instance
wilcox_result = r.wilcox_test( times1, times2 )

{
:results1 => {
:times => times1,
:mean => r.mean( times1 ),
:stddev => r.sd( times1 ),
},
:results2 => {
:times => times2,
:mean => r.mean( times2 ),
:stddev => r.sd( times2 ),
},
:p => wilcox_result[ 'p.value' ],
:W => wilcox_result[ 'statistic' ][ 'W' ],
:significant => (
wilcox_result[ 'p.value' ] < @options[ :required_significance ]
),
}
end
alias to with
end

# Options:
# :iterations
# The number of times to execute the pair of blocks.
# :inner_iterations
# Used to increase the time taken per iteration.
# :required_significance
# Maximum allowed p value in order to declare the results statistically significant.
# :verbose
# Whether to print a dot for each iteration (as a sort of progress meter).
#
# To use better-benchmark properly, it is important to set :iterations and
# :inner_iterations properly. There are a few things to bear in mind:
#
# (1) Do not set :iterations too high. It should normally be in the range
# of 10-20, but can be lower. Over 25 should be considered too high.
# (2) Execution time for one run of the blocks under test should not be too
# small (or else random variance will muddle the results). Aim for at least
# 1.0 seconds per iteration.
# (3) Minimize the proportion of any warmup time (and cooldown time) of one
# block run.
#
# In order to achieve these goals, you will need to tweak :inner_iterations
# based on your situation. The exact number you should use will depend on
# the strength of the hardware (CPU, RAM, disk), and the amount of work done
# by the blocks. For code blocks that execute extremely rapidly, you may
# need hundreds of thousands of :inner_iterations.
def self.compare_realtime( options = {}, &block1 )
require 'rsruby'

options[ :iterations ] ||= 20
options[ :inner_iterations ] ||= 1
options[ :required_significance ] ||= 0.01

if options[ :iterations ] > 30
warn "The number of iterations is set to #{options[ :iterations ]}. " +
"Using too many iterations may make the test results less reliable. " +
"It is recommended to increase the number of :inner_iterations instead."
end

ComparisonPartial.new( block1, options )
end

def self.report_on( result )
puts
puts( "Set 1 mean: %.3f s" % [ result[ :results1 ][ :mean ] ] )
puts( "Set 1 std dev: %.3f" % [ result[ :results1 ][ :stddev ] ] )
puts( "Set 2 mean: %.3f s" % [ result[ :results2 ][ :mean ] ] )
puts( "Set 2 std dev: %.3f" % [ result[ :results2 ][ :stddev ] ] )
puts "p.value: #{result[ :p ]}"
puts "W: #{result[ :W ]}"
puts(
"The difference (%+.1f%%) %s statistically significant." % [
( ( result[ :results2 ][ :mean ] - result[ :results1 ][ :mean ] ) / result[ :results1 ][ :mean ] ) * 100,
result[ :significant ] ? 'IS' : 'IS NOT'
]
)
end
end

end; end; end


Loading

0 comments on commit bb96132

Please sign in to comment.