lib/tryouts.rb in tryouts-2.3.0 vs lib/tryouts.rb in tryouts-2.3.1
- old
+ new
@@ -1,457 +1,271 @@
-require 'ostruct'
+# frozen_string_literal: true
-unless defined?(TRYOUTS_LIB_HOME)
- TRYOUTS_LIB_HOME = File.expand_path File.dirname(__FILE__)
-end
+require 'stringio'
+TRYOUTS_LIB_HOME = __dir__ unless defined?(TRYOUTS_LIB_HOME)
+
+require_relative 'tryouts/console'
+require_relative 'tryouts/section'
+require_relative 'tryouts/testbatch'
+require_relative 'tryouts/testcase'
require_relative 'tryouts/version'
class Tryouts
@debug = false
@quiet = false
@noisy = false
@fails = false
@container = Class.new
@cases = []
@sysinfo = nil
- class << self
- attr_accessor :debug, :container, :quiet, :noisy, :fails
- attr_reader :cases
+ @testcase_io = StringIO.new
+ module ClassMethods
+ attr_accessor :container, :quiet, :noisy, :fails
+ attr_writer :debug
+ attr_reader :cases, :testcase_io
+
def sysinfo
require 'sysinfo'
@sysinfo ||= SysInfo.new
@sysinfo
end
- def debug?() @debug == true end
+ def debug?
+ @debug == true
+ end
+ def update_load_path(lib_glob)
+ Dir.glob(lib_glob).each { |dir| $LOAD_PATH.unshift(dir) }
+ end
+
def run_all *paths
batches = paths.collect do |path|
parse path
end
- all, skipped_tests, failed_tests = 0, 0, 0
- skipped_batches, failed_batches = 0, 0
+ all = 0
+ skipped_tests = 0
+ failed_tests = 0
+ skipped_batches = 0
+ failed_batches = 0
- msg 'Ruby %s @ %-40s' % [RUBY_VERSION, Time.now], $/
+ msg format('Ruby %s @ %-60s', RUBY_VERSION, Time.now), $/
if Tryouts.debug?
Tryouts.debug "Found #{paths.size} files:"
paths.each { |path| Tryouts.debug " #{path}" }
Tryouts.debug
end
batches.each do |batch|
+ path = batch.path.gsub(%r{#{Dir.pwd}/?}, '')
+ divider = '-' * 70
+ path_pretty = format('>>>>> %-20s %s', path, '').ljust(70, '<')
- path = batch.path.gsub(/#{Dir.pwd}\/?/, '')
+ msg $/
+ vmsg Console.reverse(divider)
+ vmsg Console.reverse(path_pretty)
+ vmsg Console.reverse(divider)
+ vmsg $/
- vmsg '%-60s %s' % [path, '']
-
- before_handler = Proc.new do |t|
- if Tryouts.noisy && !Tryouts.fails
- vmsg Console.reverse(' %-58s ' % [t.desc.to_s])
- vmsg t.test.inspect, t.exps.inspect
+ before_handler = proc do |tc|
+ if Tryouts.noisy
+ tc_title = tc.desc.to_s
+ vmsg Console.underline(format('%-58s ', tc_title))
+ vmsg tc.test.inspect, tc.exps.inspect
end
end
- batch.run(before_handler) do |t|
- if t.failed?
- failed_tests += 1
- if Tryouts.noisy && Tryouts.fails
- vmsg Console.color(:red, t.failed.join($/)), $/
- else
- msg ' %s (%s:%s)' % [Console.color(:red, "FAIL"), path, t.exps.first]
- end
- elsif (t.skipped? || !t.run?) && !Tryouts.fails
- skipped_tests += 1
- if Tryouts.noisy
- vmsg Console.bright(t.skipped.join($/)), $/
- else
- msg ' SKIP (%s:%s)' % [path, t.exps.first]
- end
- elsif !Tryouts.fails
- if Tryouts.noisy
- vmsg Console.color(:green, t.passed.join($/)), $/
- else
- msg ' %s' % [Console.color(:green, 'PASS')]
- end
- end
+ batch.run(before_handler) do |tc|
all += 1
+ failed_tests += 1 if tc.failed?
+ skipped_tests += 1 if tc.skipped?
+ codelines = tc.outlines.join($/)
+ first_exp_line = tc.exps.first
+ result_adjective = tc.failed? ? 'FAILED' : 'PASSED'
+
+ first_exp_line = tc.exps.first
+ location = format('%s:%d', tc.exps.path, first_exp_line)
+
+ expectation = Console.color(tc.color, codelines)
+ summary = Console.color(tc.color, "%s @ %s" % [tc.adjective, location])
+ vmsg ' %s' % expectation
+ if tc.failed?
+ msg Console.reverse(summary)
+ else
+ msg summary
+ end
+ vmsg
+
+ # Output buffered testcase_io to stdout
+ # and reset it for the next test case.
+ unless Tryouts.fails && !tc.failed?
+ $stdout.puts testcase_io.string unless Tryouts.quiet
+ end
+
+ # Reset the testcase IO buffer
+ testcase_io.truncate(0)
end
end
- msg
- if all > 0
- suffix = 'tests passed'
- suffix << " (and #{skipped_tests} skipped)" if skipped_tests > 0
- msg cformat(all-failed_tests-skipped_tests, all-skipped_tests, suffix) if all-skipped_tests > 0
- end
- if batches.size > 1
- if batches.size-skipped_batches > 0
- suffix = "batches passed"
- suffix << " (and #{skipped_batches} skipped)" if skipped_batches > 0
- msg cformat(batches.size-skipped_batches-failed_batches, batches.size-skipped_batches, suffix)
+ # Create a line of separation before the result summary
+ msg $INPUT_RECORD_SEPARATOR # newline
+
+ if all
+ suffix = "tests passed (#{skipped_tests} skipped)" if skipped_tests > 0
+ actual_test_size = all - skipped_tests
+ if actual_test_size > 0
+ msg cformat(all - failed_tests - skipped_tests, all - skipped_tests, suffix)
end
end
- failed_tests # 0 means success
+ actual_batch_size = (batches.size - skipped_batches)
+ if batches.size > 1 && actual_batch_size > 0
+ suffix = 'batches passed'
+ suffix << " (#{skipped_batches} skipped)" if skipped_batches > 0
+ msg cformat(batches.size - skipped_batches - failed_batches, batches.size - skipped_batches, suffix)
+ end
+
+ # Print out the buffered result summary
+ $stdout.puts testcase_io.string
+
+ failed_tests # returns the number of failed tests (0 if all passed)
end
def cformat(*args)
Console.bright '%d of %d %s' % args
end
- def run path
+ def run(path)
batch = parse path
batch.run
batch
end
- def parse path
- #debug "Loading #{path}"
+ def parse(path)
+ # debug "Loading #{path}"
lines = File.readlines path
skip_ahead = 0
batch = TestBatch.new path, lines
lines.size.times do |idx|
skip_ahead -= 1 and next if skip_ahead > 0
+
line = lines[idx].chomp
- #debug('%-4d %s' % [idx, line])
- if expectation? line
- offset = 0
- exps = Section.new(path, idx+1)
- exps << line.chomp
- while (idx+offset < lines.size)
- offset += 1
- this_line = lines[idx+offset]
- break if ignore?(this_line)
- if expectation?(this_line)
- exps << this_line.chomp
- skip_ahead += 1
- end
- exps.last += 1
+ # debug('%-4d %s' % [idx, line])
+ next unless expectation? line
+
+ offset = 0
+ exps = Section.new(path, idx + 1)
+ exps << line.chomp
+ while idx + offset < lines.size
+ offset += 1
+ this_line = lines[idx + offset]
+ break if ignore?(this_line)
+
+ if expectation?(this_line)
+ exps << this_line.chomp
+ skip_ahead += 1
end
+ exps.last += 1
+ end
- offset = 0
- buffer, desc = Section.new(path), Section.new(path)
- test = Section.new(path, idx) # test start the line before the exp.
- blank_buffer = Section.new(path)
- while (idx-offset >= 0)
- offset += 1
- this_line = lines[idx-offset].chomp
- buffer.unshift this_line if ignore?(this_line)
- if comment?(this_line)
- buffer.unshift this_line
+ offset = 0
+ buffer = Section.new(path)
+ desc = Section.new(path)
+ test = Section.new(path, idx) # test start the line before the exp.
+ while idx - offset >= 0
+ offset += 1
+ this_line = lines[idx - offset].chomp
+ buffer.unshift this_line if ignore?(this_line)
+ buffer.unshift this_line if comment?(this_line)
+ if test?(this_line)
+ test.unshift(*buffer) && buffer.clear
+ test.unshift this_line
+ end
+ if test_begin?(this_line)
+ while test_begin?(lines[idx - (offset + 1)].chomp)
+ offset += 1
+ buffer.unshift lines[idx - offset].chomp
end
- if test?(this_line)
- test.unshift(*buffer) && buffer.clear
- test.unshift this_line
- end
- if test_begin?(this_line)
- while test_begin?(lines[idx-(offset+1)].chomp)
- offset += 1
- buffer.unshift lines[idx-offset].chomp
- end
- end
- if test_begin?(this_line) || idx-offset == 0 || expectation?(this_line)
- adjust = expectation?(this_line) ? 2 : 1
- test.first = idx-offset+buffer.size+adjust
- desc.unshift *buffer
- desc.last = test.first-1
- desc.first = desc.last-desc.size+1
- # remove empty lines between the description
- # and the previous expectation
- while !desc.empty? && desc[0].empty?
- desc.shift
- desc.first += 1
- end
- break
- end
end
+ next unless test_begin?(this_line) || idx - offset == 0 || expectation?(this_line)
- batch << TestCase.new(desc, test, exps)
+ adjust = expectation?(this_line) ? 2 : 1
+ test.first = idx - offset + buffer.size + adjust
+ desc.unshift(*buffer)
+ desc.last = test.first - 1
+ desc.first = desc.last - desc.size + 1
+ # remove empty lines between the description
+ # and the previous expectation
+ while !desc.empty? && desc[0].empty?
+ desc.shift
+ desc.first += 1
+ end
+ break
end
+
+ batch << TestCase.new(desc, test, exps)
end
batch
end
-
- def print str
+ def print(str)
return if Tryouts.quiet
- STDOUT.print str
- STDOUT.flush
+
+ $stdout.print str
+ $stdout.flush
end
- def vmsg *msg
- STDOUT.puts *msg if !Tryouts.quiet && Tryouts.noisy
+ def vmsg *msgs
+ msg(*msgs) if Tryouts.noisy
end
- def msg *msg
- STDOUT.puts *msg unless Tryouts.quiet
+ def msg *msgs
+ testcase_io.puts(*msgs) unless Tryouts.quiet
end
- def err *msg
+ def err *msgs
msg.each do |line|
- STDERR.puts Console.color :red, line
+ $stderr.puts Console.color :red, line
end
end
- def debug *msg
- STDERR.puts *msg if @debug
+ def debug *msgs
+ $stderr.puts(*msgs) if debug?
end
def eval(str, path, line)
- begin
- Kernel.eval str, @container.send(:binding), path, line
- rescue SyntaxError, LoadError => ex
- Tryouts.err Console.color(:red, ex.message),
- Console.color(:red, ex.backtrace.first)
- nil
- end
+ Kernel.eval str, @container.send(:binding), path, line
+ rescue SyntaxError, LoadError => e
+ Tryouts.err Console.color(:red, e.message),
+ Console.color(:red, e.backtrace.first)
+ nil
end
private
- def expectation? str
+ def expectation?(str)
!ignore?(str) && str.strip.match(/\A\#+\s*=>/)
end
- def comment? str
+ def comment?(str)
!str.strip.match(/^\#+/).nil? && !expectation?(str)
end
- def test? str
+ def test?(str)
!ignore?(str) && !expectation?(str) && !comment?(str)
end
- def ignore? str
+ def ignore?(str)
str.to_s.strip.chomp.empty?
end
- def test_begin? str
- ret = !str.strip.match(/\#+\s*TEST/i).nil? ||
- !str.strip.match(/\A\#\#+[\s\w]+/i).nil?
- ret
+ def test_begin?(str)
+ !str.strip.match(/\#+\s*TEST/i).nil? ||
+ !str.strip.match(/\A\#\#+[\s\w]+/i).nil?
end
-
-
end
- class TestBatch < Array
- class Container
- def metaclass
- class << self; end
- end
- end
- attr_reader :path
- attr_reader :failed
- attr_reader :lines
- def initialize(p,l)
- @path, @lines = p, l
- @container = Container.new.metaclass
- @run = false
- end
- def run(before_test, &after_test)
- return if empty?
- setup
- ret = self.select { |tc|
- before_test.call(tc) unless before_test.nil?
- ret = !tc.run
- after_test.call(tc)
- ret # select failed tests
- }
- @failed = ret.size
- @run = true
- clean
- !failed?
- end
- def failed?
- !@failed.nil? && @failed > 0
- end
- def setup
- return if empty?
- start = first.desc.nil? ? first.test.first : first.desc.first-1
- Tryouts.eval lines[0..start-1].join, path, 0 if start > 0
- end
- def clean
- return if empty?
- last_line = last.exps.last+1
- if last_line < lines.size
- Tryouts.eval lines[last_line..-1].join, path, last_line
- end
- end
- def run?
- @run
- end
- end
- class TestCase
- attr_reader :desc, :test, :exps, :failed, :passed, :skipped
- def initialize(d,t,e)
- @desc, @test, @exps, @path = d,t,e
- end
- def inspect
- [@desc.inspect, @test.inspect, @exps.inspect].join
- end
- def to_s
- [@desc.to_s, @test.to_s, @exps.to_s].join
- end
- def run
- Tryouts.debug '%s:%d' % [@test.path, @test.first]
- Tryouts.debug inspect, $/
- expectations = exps.collect { |exp,idx|
- exp =~ /\A\#?\s*=>\s*(.+)\Z/
- $1 # this will be nil if the expectation is commented out
- }
-
- # Evaluate test block only if there are valid expectations
- unless expectations.compact.empty?
- test_value = Tryouts.eval @test.to_s, @test.path, @test.first
- @has_run = true
- end
-
- @passed, @failed, @skipped = [], [], []
- expectations.each_with_index { |exp,idx|
- if exp.nil?
- @skipped << ' [skipped]'
- else
- exp_value = Tryouts.eval(exp, @exps.path, @exps.first+idx)
- if test_value == exp_value
- @passed << ' == %s' % [test_value.inspect]
- else
- @failed << ' != %s' % [test_value.inspect]
- end
- end
- }
- Tryouts.debug
- @failed.empty?
- end
- def skipped?
- !@skipped.nil? && !@skipped.empty?
- end
- def run?
- @has_run == true
- end
- def failed?
- !@failed.nil? && !@failed.empty?
- end
- private
- def create_proc str, path, line
- eval("Proc.new {\n #{str}\n}", binding, path, line)
- end
- end
- class Section < Array
- attr_accessor :path, :first, :last
- def initialize path, start=0
- @path = path
- @first, @last = start, start
- end
- def range
- @first..@last
- end
- def inspect
- range.to_a.zip(self).collect do |line|
- "%-4d %s\n" % line
- end.join
- end
- def to_s
- self.join($/)
- end
- end
-
-
- module Console
-
- # ANSI escape sequence numbers for text attributes
- ATTRIBUTES = {
- :normal => 0,
- :bright => 1,
- :dim => 2,
- :underline => 4,
- :blink => 5,
- :reverse => 7,
- :hidden => 8,
- :default => 0,
- }.freeze unless defined? ATTRIBUTES
-
- # ANSI escape sequence numbers for text colours
- COLOURS = {
- :black => 30,
- :red => 31,
- :green => 32,
- :yellow => 33,
- :blue => 34,
- :magenta => 35,
- :cyan => 36,
- :white => 37,
- :default => 39,
- :random => 30 + rand(10).to_i
- }.freeze unless defined? COLOURS
-
- # ANSI escape sequence numbers for background colours
- BGCOLOURS = {
- :black => 40,
- :red => 41,
- :green => 42,
- :yellow => 43,
- :blue => 44,
- :magenta => 45,
- :cyan => 46,
- :white => 47,
- :default => 49,
- :random => 40 + rand(10).to_i
- }.freeze unless defined? BGCOLOURS
-
- module InstanceMethods
- def bright
- Console.bright(self)
- end
- def reverse
- Console.reverse(self)
- end
- def color(col)
- Console.color(col, self)
- end
- def att(col)
- Console.att(col, self)
- end
- def bgcolor(col)
- Console.bgcolor(col, self)
- end
- end
-
- def self.bright(str)
- str = [style(ATTRIBUTES[:bright]), str, default_style].join
- str.extend Console::InstanceMethods
- str
- end
- def self.reverse(str)
- str = [style(ATTRIBUTES[:reverse]), str, default_style].join
- str.extend Console::InstanceMethods
- str
- end
- def self.color(col, str)
- str = [style(COLOURS[col]), str, default_style].join
- str.extend Console::InstanceMethods
- str
- end
- def self.att(name, str)
- str = [style(ATTRIBUTES[name]), str, default_style].join
- str.extend Console::InstanceMethods
- str
- end
- def self.bgcolor(col, str)
- str = [style(ATTRIBUTES[col]), str, default_style].join
- str.extend Console::InstanceMethods
- str
- end
- private
- def self.style(*att)
- # => \e[8;34;42m
- "\e[%sm" % att.join(';')
- end
- def self.default_style
- style(ATTRIBUTES[:default], ATTRIBUTES[:COLOURS], ATTRIBUTES[:BGCOLOURS])
- end
- end
-
+ extend ClassMethods
end