Merge branch 'output_refactoring'

* output_refactoring:
  Updated for JRuby
  Switch blue color to cyan for better contrast on dark terminals.
  80 char limit to end screen
  Updated Koans directory
  Added else clause if progress file is not there.
  regen for updated koans/edgecase.rb
  end screen with koans logo ascii art
This commit is contained in:
Jim Weirich
2010-09-27 10:49:17 -04:00
5 changed files with 424 additions and 129 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
dist dist
.project_env.rc .project_env.rc
.path_progress

View File

@@ -73,7 +73,7 @@ module EdgeCase
end end
class Sensei class Sensei
attr_reader :failure, :failed_test attr_reader :failure, :failed_test, :pass_count
in_ruby_version("1.8") do in_ruby_version("1.8") do
AssertionError = Test::Unit::AssertionFailedError AssertionError = Test::Unit::AssertionFailedError
@@ -91,16 +91,43 @@ module EdgeCase
@pass_count = 0 @pass_count = 0
@failure = nil @failure = nil
@failed_test = nil @failed_test = nil
@observations = []
end end
def accumulate(test) PROGRESS_FILE_NAME = '.path_progress'
if test.passed?
def add_progress(prog)
@_contents = nil
exists = File.exists?(PROGRESS_FILE_NAME)
File.open(PROGRESS_FILE_NAME,'a+') do |f|
f.print "#{',' if exists}#{prog}"
end
end
def progress
if @_contents.nil?
if File.exists?(PROGRESS_FILE_NAME)
File.open(PROGRESS_FILE_NAME,'r') do |f|
@_contents = f.read.to_s.gsub(/\s/,'').split(',')
end
else
@_contents = []
end
end
@_contents
end
def observe(step)
if step.passed?
@pass_count += 1 @pass_count += 1
puts Color.green(" #{test.name} has expanded your awareness.") if @pass_count > progress.last.to_i
@observations << Color.green("#{step.koan_file}##{step.name} has expanded your awareness.")
end
else else
puts Color.red(" #{test.name} has damaged your karma.") @failed_test = step
@failed_test = test @failure = step.failure
@failure = test.failure add_progress(@pass_count)
@observations << Color.red("#{step.koan_file}##{step.name} has damaged your karma.")
throw :edgecase_exit throw :edgecase_exit
end end
end end
@@ -113,22 +140,115 @@ module EdgeCase
failure.is_a?(AssertionError) failure.is_a?(AssertionError)
end end
def report def instruct
if failed? if failed?
puts @observations.each{|c| puts c }
puts Color.green("You have not yet reached enlightenment ...") encourage
puts Color.red(failure.message) guide_through_error
puts a_zenlike_statement
puts Color.green("Please meditate on the following code:") show_progress
if assert_failed? else
#puts find_interesting_lines(failure.backtrace) end_screen
puts find_interesting_lines(failure.backtrace).collect {|l| Color.red(l) }
else
puts Color.red(failure.backtrace)
end
puts
end end
puts Color.green(a_zenlike_statement) end
def show_progress
bar_width = 50
total_tests = EdgeCase::Koan.total_tests
scale = bar_width.to_f/total_tests
print Color.green("your path thus far [")
happy_steps = (pass_count*scale).to_i
happy_steps = 1 if happy_steps == 0 && pass_count > 0
print Color.green('.'*happy_steps)
if failed?
print Color.red('X')
print Color.cyan('_'*(bar_width-1-happy_steps))
end
print Color.green(']')
print " #{pass_count}/#{total_tests}"
puts
end
def end_screen
completed = <<-ENDTEXT
,, , ,,
: ::::, :::,
, ,,: :::::::::::::,, :::: : ,
, ,,, ,:::::::::::::::::::, ,: ,: ,,
:, ::, , , :, ,::::::::::::::::::, ::: ,::::
: : ::, ,:::::::: ::, ,::::
, ,::::: :,:::::::,::::,
,: , ,:,,: :::::::::::::
::,: ,,:::, ,::::::::::::,
,:::, :,,::: ::::::::::::,
,::: :::::::, Mountains are again merely mountains ,::::::::::::
:::,,,:::::: ::::::::::::
,:::::::::::, ::::::::::::,
:::::::::::, ,::::::::::::
::::::::::::: ,::::::::::::
:::::::::::: Ruby Koans ::::::::::::,
:::::::::::: ,::::::::::::,
:::::::::::, , ::::::::::::
,:::::::::::::, brought to you by ,,::::::::::::,
:::::::::::::: ,::::::::::::
::::::::::::::, ,:::::::::::::
::::::::::::, EdgeCase Software Artisans , ::::::::::::
:,::::::::: :::: :::::::::::::
,::::::::::: ,: ,,:::::::::::::,
:::::::::::: ,::::::::::::::,
:::::::::::::::::, ::::::::::::::::
:::::::::::::::::::, ::::::::::::::::
::::::::::::::::::::::, ,::::,:, , ::::,:::
:::::::::::::::::::::::, ::,: ::,::, ,,: ::::
,:::::::::::::::::::: ::,, , ,, ,::::
,:::::::::::::::: ::,, , ,:::,
,:::: , ,,
,,,
ENDTEXT
puts completed
end
def encourage
puts
puts "The Master says:"
puts Color.cyan(" You have not yet reached enlightenment.")
if ((recents = progress.last(5)) && recents.size == 5 && recents.uniq.size == 1)
puts Color.cyan(" I sense frustration. Do not be afraid to ask for help.")
elsif progress.last(2).size == 2 && progress.last(2).uniq.size == 1
puts Color.cyan(" Do not lose hope.")
elsif progress.last.to_i > 0
puts Color.cyan(" You are progressing. Excellent. #{progress.last} completed.")
end
end
def guide_through_error
puts
puts "The answers you seek..."
puts Color.red(indent(failure.message).join)
puts
puts "Please meditate on the following code:"
if assert_failed?
puts embolden_first_line_only(indent(find_interesting_lines(failure.backtrace)))
else
puts embolden_first_line_only(indent(failure.backtrace))
end
puts
end
def embolden_first_line_only(text)
first_line = true
text.collect { |t|
if first_line
first_line = false
Color.red(t)
else
Color.cyan(t)
end
}
end
def indent(text)
text.collect{|t| " #{t}"}
end end
def find_interesting_lines(backtrace) def find_interesting_lines(backtrace)
@@ -140,7 +260,6 @@ module EdgeCase
# Hat's tip to Ara T. Howard for the zen statements from his # Hat's tip to Ara T. Howard for the zen statements from his
# metakoans Ruby Quiz (http://rubyquiz.com/quiz67.html) # metakoans Ruby Quiz (http://rubyquiz.com/quiz67.html)
def a_zenlike_statement def a_zenlike_statement
puts
if !failed? if !failed?
zen_statement = "Mountains are again merely mountains" zen_statement = "Mountains are again merely mountains"
else else
@@ -159,18 +278,21 @@ module EdgeCase
"things are not what they appear to be: nor are they otherwise" "things are not what they appear to be: nor are they otherwise"
end end
end end
zen_statement puts Color.green(zen_statement)
end end
end end
class Koan class Koan
include Test::Unit::Assertions include Test::Unit::Assertions
attr_reader :name, :failure attr_reader :name, :failure, :koan_count, :step_count, :koan_file
def initialize(name) def initialize(name, koan_file=nil, koan_count=0, step_count=0)
@name = name @name = name
@failure = nil @failure = nil
@koan_count = koan_count
@step_count = step_count
@koan_file = koan_file
end end
def passed? def passed?
@@ -187,6 +309,22 @@ module EdgeCase
def teardown def teardown
end end
def meditate
setup
begin
send(name)
rescue StandardError, EdgeCase::Sensei::AssertionError => ex
failed(ex)
ensure
begin
teardown
rescue StandardError, EdgeCase::Sensei::AssertionError => ex
failed(ex) if passed?
end
end
self
end
# Class methods for the EdgeCase test suite. # Class methods for the EdgeCase test suite.
class << self class << self
def inherited(subclass) def inherited(subclass)
@@ -194,32 +332,7 @@ module EdgeCase
end end
def method_added(name) def method_added(name)
testmethods << name unless tests_disabled? testmethods << name if !tests_disabled? && Koan.test_pattern =~ name.to_s
end
def run_tests(accumulator)
puts
puts Color.green("Thinking #{self}")
testmethods.each do |m|
self.run_test(m, accumulator) if Koan.test_pattern =~ m.to_s
end
end
def run_test(method, accumulator)
test = self.new(method)
test.setup
begin
test.send(method)
rescue StandardError, EdgeCase::Sensei::AssertionError => ex
test.failed(ex)
ensure
begin
test.teardown
rescue StandardError, EdgeCase::Sensei::AssertionError => ex
test.failed(ex) if test.passed?
end
end
accumulator.accumulate(test)
end end
def end_of_enlightenment def end_of_enlightenment
@@ -261,17 +374,36 @@ module EdgeCase
@test_pattern ||= /^test_/ @test_pattern ||= /^test_/
end end
def total_tests
self.subclasses.inject(0){|total, k| total + k.testmethods.size }
end
end
end
class ThePath
def walk
sensei = EdgeCase::Sensei.new
each_step do |step|
sensei.observe(step.meditate)
end
sensei.instruct
end
def each_step
catch(:edgecase_exit) {
step_count = 0
EdgeCase::Koan.subclasses.each_with_index do |koan,koan_index|
koan.testmethods.each do |method_name|
step = koan.new(method_name, koan.to_s, koan_index+1, step_count+=1)
yield step
end
end
}
end end
end end
end end
END { END {
EdgeCase::Koan.command_line(ARGV) EdgeCase::Koan.command_line(ARGV)
zen_master = EdgeCase::Sensei.new EdgeCase::ThePath.new.walk
catch(:edgecase_exit) {
EdgeCase::Koan.subclasses.each do |sc|
sc.run_tests(zen_master)
end
}
zen_master.report
} }

View File

@@ -3,7 +3,7 @@ require File.expand_path(File.dirname(__FILE__) + '/edgecase')
def my_global_method(a,b) def my_global_method(a,b)
a + b a + b
end end
class AboutMethods < EdgeCase::Koan class AboutMethods < EdgeCase::Koan
def test_calling_global_methods def test_calling_global_methods
@@ -36,19 +36,19 @@ class AboutMethods < EdgeCase::Koan
# Rewrite the eval string to continue. # Rewrite the eval string to continue.
# #
end end
# NOTE: wrong number of argument is not a SYNTAX error, but a # NOTE: wrong number of argument is not a SYNTAX error, but a
# runtime error. # runtime error.
def test_calling_global_methods_with_wrong_number_of_arguments def test_calling_global_methods_with_wrong_number_of_arguments
exception = assert_raise(___(ArgumentError)) do exception = assert_raise(___(ArgumentError)) do
my_global_method my_global_method
end end
assert_match(/#{__("wrong number of arguments")}/, exception.message) assert_match(/#{__("wrong (number|#) of arguments")}/, exception.message)
exception = assert_raise(___(ArgumentError)) do exception = assert_raise(___(ArgumentError)) do
my_global_method(1,2,3) my_global_method(1,2,3)
end end
assert_match(/#{__("wrong number of arguments")}/, exception.message) assert_match(/#{__("wrong (number|#) of arguments")}/, exception.message)
end end
# ------------------------------------------------------------------ # ------------------------------------------------------------------
@@ -142,7 +142,7 @@ class AboutMethods < EdgeCase::Koan
"tail" "tail"
end end
end end
def test_calling_methods_in_other_objects_require_explicit_receiver def test_calling_methods_in_other_objects_require_explicit_receiver
rover = Dog.new rover = Dog.new
assert_equal __("Fido"), rover.name assert_equal __("Fido"), rover.name

View File

@@ -25,15 +25,21 @@ class AboutSymbols < EdgeCase::Koan
def test_method_names_become_symbols def test_method_names_become_symbols
all_symbols = Symbol.all_symbols all_symbols = Symbol.all_symbols
assert_equal __(true), all_symbols.include?(:test_method_names_become_symbols)
assert_equal __(true), all_symbols.include?(:test_method_names_are_symbols)
end end
RubyConstant = "What is the sound of one hand clapping?" # THINK ABOUT IT:
def test_constants_become_symbols #
all_symbols = Symbol.all_symbols # Why do we capture the list of symbols before we check for the
# method name?
assert_equal true, all_symbols.include?(__(:RubyConstant)) in_ruby_version("mri") do
RubyConstant = "What is the sound of one hand clapping?"
def test_constants_become_symbols
all_symbols = Symbol.all_symbols
assert_equal __(true), all_symbols.include?(__(:RubyConstant))
end
end end
def test_symbols_can_be_made_from_strings def test_symbols_can_be_made_from_strings
@@ -47,6 +53,13 @@ class AboutSymbols < EdgeCase::Koan
assert_equal symbol, __("cats and dogs").to_sym assert_equal symbol, __("cats and dogs").to_sym
end end
def test_symbols_with_spaces_can_be_built
value = "and"
symbol = :"cats #{value} dogs"
assert_equal symbol, __("cats and dogs").to_sym
end
def test_to_s_is_called_on_interpolated_symbols def test_to_s_is_called_on_interpolated_symbols
symbol = :cats symbol = :cats
string = "It is raining #{symbol} and dogs." string = "It is raining #{symbol} and dogs."
@@ -65,13 +78,23 @@ class AboutSymbols < EdgeCase::Koan
assert_equal __(false), symbol.respond_to?(:each_char) assert_equal __(false), symbol.respond_to?(:each_char)
assert_equal __(false), symbol.respond_to?(:reverse) assert_equal __(false), symbol.respond_to?(:reverse)
end end
# It's important to realize that symbols are not "immutable # It's important to realize that symbols are not "immutable
# strings", though they are immutable. None of the # strings", though they are immutable. None of the
# interesting string operations are available on symbols. # interesting string operations are available on symbols.
def test_symbols_cannot_be_concatenated def test_symbols_cannot_be_concatenated
# Exceptions will be pondered further father down the path # Exceptions will be pondered further father down the path
assert_raise(___(NoMethodError)) do assert_raise(___(NoMethodError)) do
:cats + :dogs :cats + :dogs
end end
end end
def test_symbols_can_be_dynamically_created
assert_equal __(:catsdogs), ("cats" + "dogs").to_sym
end
# THINK ABOUT IT:
#
# Why is it not a good idea to dynamically create a lot of symbols?
end end

View File

@@ -6,8 +6,14 @@ require 'test/unit/assertions'
class FillMeInError < StandardError class FillMeInError < StandardError
end end
def in_ruby_version(version) def ruby_version?(version)
yield if RUBY_VERSION =~ /^#{version}/ RUBY_VERSION =~ /^#{version}/ ||
(version == 'jruby' && defined?(JRUBY_VERSION)) ||
(version == 'mri' && ! defined?(JRUBY_VERSION))
end
def in_ruby_version(*versions)
yield if versions.any? { |v| ruby_version?(v) }
end end
def __(value="FILL ME IN", value19=:mu) def __(value="FILL ME IN", value19=:mu)
@@ -73,7 +79,7 @@ module EdgeCase
end end
class Sensei class Sensei
attr_reader :failure, :failed_test attr_reader :failure, :failed_test, :pass_count
in_ruby_version("1.8") do in_ruby_version("1.8") do
AssertionError = Test::Unit::AssertionFailedError AssertionError = Test::Unit::AssertionFailedError
@@ -91,16 +97,43 @@ module EdgeCase
@pass_count = 0 @pass_count = 0
@failure = nil @failure = nil
@failed_test = nil @failed_test = nil
@observations = []
end end
def accumulate(test) PROGRESS_FILE_NAME = '.path_progress'
if test.passed?
def add_progress(prog)
@_contents = nil
exists = File.exists?(PROGRESS_FILE_NAME)
File.open(PROGRESS_FILE_NAME,'a+') do |f|
f.print "#{',' if exists}#{prog}"
end
end
def progress
if @_contents.nil?
if File.exists?(PROGRESS_FILE_NAME)
File.open(PROGRESS_FILE_NAME,'r') do |f|
@_contents = f.read.to_s.gsub(/\s/,'').split(',')
end
else
@_contents = []
end
end
@_contents
end
def observe(step)
if step.passed?
@pass_count += 1 @pass_count += 1
puts Color.green(" #{test.name} has expanded your awareness.") if @pass_count > progress.last.to_i
@observations << Color.green("#{step.koan_file}##{step.name} has expanded your awareness.")
end
else else
puts Color.red(" #{test.name} has damaged your karma.") @failed_test = step
@failed_test = test @failure = step.failure
@failure = test.failure add_progress(@pass_count)
@observations << Color.red("#{step.koan_file}##{step.name} has damaged your karma.")
throw :edgecase_exit throw :edgecase_exit
end end
end end
@@ -113,22 +146,116 @@ module EdgeCase
failure.is_a?(AssertionError) failure.is_a?(AssertionError)
end end
def report def instruct
if failed? if failed?
puts @observations.each{|c| puts c }
puts Color.green("You have not yet reached enlightenment ...") encourage
puts Color.red(failure.message) guide_through_error
puts a_zenlike_statement
puts Color.green("Please meditate on the following code:") show_progress
if assert_failed? else
#puts find_interesting_lines(failure.backtrace) end_screen
puts find_interesting_lines(failure.backtrace).collect {|l| Color.red(l) }
else
puts Color.red(failure.backtrace)
end
puts
end end
puts Color.green(a_zenlike_statement) end
def show_progress
bar_width = 50
total_tests = EdgeCase::Koan.total_tests
scale = bar_width.to_f/total_tests
print Color.green("your path thus far [")
happy_steps = (pass_count*scale).to_i
happy_steps = 1 if happy_steps == 0 && pass_count > 0
print Color.green('.'*happy_steps)
if failed?
print Color.red('X')
print Color.cyan('_'*(bar_width-1-happy_steps))
end
print Color.green(']')
print " #{pass_count}/#{total_tests}"
puts
end
def end_screen
completed = <<-ENDTEXT
,, , ,,
: ::::, :::,
, ,,: :::::::::::::,, :::: : ,
, ,,, ,:::::::::::::::::::, ,: ,: ,,
:, ::, , , :, ,::::::::::::::::::, ::: ,::::
: : ::, ,:::::::: ::, ,::::
, ,::::: :,:::::::,::::,
,: , ,:,,: :::::::::::::
::,: ,,:::, ,::::::::::::,
,:::, :,,::: ::::::::::::,
,::: :::::::, Mountains are again merely mountains ,::::::::::::
:::,,,:::::: ::::::::::::
,:::::::::::, ::::::::::::,
:::::::::::, ,::::::::::::
::::::::::::: ,::::::::::::
:::::::::::: Ruby Koans ::::::::::::,
:::::::::::: ,::::::::::::,
:::::::::::, , ::::::::::::
,:::::::::::::, brought to you by ,,::::::::::::,
:::::::::::::: ,::::::::::::
::::::::::::::, ,:::::::::::::
::::::::::::, EdgeCase Software Artisans , ::::::::::::
:,::::::::: :::: :::::::::::::
,::::::::::: ,: ,,:::::::::::::,
:::::::::::: ,::::::::::::::,
:::::::::::::::::, ::::::::::::::::
:::::::::::::::::::, ::::::::::::::::
::::::::::::::::::::::, ,::::,:, , ::::,:::
:::::::::::::::::::::::, ::,: ::,::, ,,: ::::
,:::::::::::::::::::: ::,, , ,, ,::::
,:::::::::::::::: ::,, , ,:::,
,:::: , ,,
,,,
ENDTEXT
puts completed
end
def encourage
puts
puts "The Master says:"
puts Color.cyan(" You have not yet reached enlightenment.")
if ((recents = progress.last(5)) && recents.size == 5 && recents.uniq.size == 1)
puts Color.cyan(" I sense frustration. Do not be afraid to ask for help.")
elsif progress.last(2).size == 2 && progress.last(2).uniq.size == 1
puts Color.cyan(" Do not lose hope.")
elsif progress.last.to_i > 0
puts Color.cyan(" You are progressing. Excellent. #{progress.last} completed.")
end
end
def guide_through_error
puts
puts "The answers you seek..."
puts Color.red(indent(failure.message).join)
puts
puts "Please meditate on the following code:"
if assert_failed?
puts embolden_first_line_only(indent(find_interesting_lines(failure.backtrace)))
else
puts embolden_first_line_only(indent(failure.backtrace))
end
puts
end
def embolden_first_line_only(text)
first_line = true
text.collect { |t|
if first_line
first_line = false
Color.red(t)
else
Color.cyan(t)
end
}
end
def indent(text)
text = text.split(/\n/) if text.is_a?(String)
text.collect{|t| " #{t}"}
end end
def find_interesting_lines(backtrace) def find_interesting_lines(backtrace)
@@ -140,7 +267,6 @@ module EdgeCase
# Hat's tip to Ara T. Howard for the zen statements from his # Hat's tip to Ara T. Howard for the zen statements from his
# metakoans Ruby Quiz (http://rubyquiz.com/quiz67.html) # metakoans Ruby Quiz (http://rubyquiz.com/quiz67.html)
def a_zenlike_statement def a_zenlike_statement
puts
if !failed? if !failed?
zen_statement = "Mountains are again merely mountains" zen_statement = "Mountains are again merely mountains"
else else
@@ -159,18 +285,21 @@ module EdgeCase
"things are not what they appear to be: nor are they otherwise" "things are not what they appear to be: nor are they otherwise"
end end
end end
zen_statement puts Color.green(zen_statement)
end end
end end
class Koan class Koan
include Test::Unit::Assertions include Test::Unit::Assertions
attr_reader :name, :failure attr_reader :name, :failure, :koan_count, :step_count, :koan_file
def initialize(name) def initialize(name, koan_file=nil, koan_count=0, step_count=0)
@name = name @name = name
@failure = nil @failure = nil
@koan_count = koan_count
@step_count = step_count
@koan_file = koan_file
end end
def passed? def passed?
@@ -187,6 +316,22 @@ module EdgeCase
def teardown def teardown
end end
def meditate
setup
begin
send(name)
rescue StandardError, EdgeCase::Sensei::AssertionError => ex
failed(ex)
ensure
begin
teardown
rescue StandardError, EdgeCase::Sensei::AssertionError => ex
failed(ex) if passed?
end
end
self
end
# Class methods for the EdgeCase test suite. # Class methods for the EdgeCase test suite.
class << self class << self
def inherited(subclass) def inherited(subclass)
@@ -194,32 +339,7 @@ module EdgeCase
end end
def method_added(name) def method_added(name)
testmethods << name unless tests_disabled? testmethods << name if !tests_disabled? && Koan.test_pattern =~ name.to_s
end
def run_tests(accumulator)
puts
puts Color.green("Thinking #{self}")
testmethods.each do |m|
self.run_test(m, accumulator) if Koan.test_pattern =~ m.to_s
end
end
def run_test(method, accumulator)
test = self.new(method)
test.setup
begin
test.send(method)
rescue StandardError, EdgeCase::Sensei::AssertionError => ex
test.failed(ex)
ensure
begin
test.teardown
rescue StandardError, EdgeCase::Sensei::AssertionError => ex
test.failed(ex) if test.passed?
end
end
accumulator.accumulate(test)
end end
def end_of_enlightenment def end_of_enlightenment
@@ -261,17 +381,36 @@ module EdgeCase
@test_pattern ||= /^test_/ @test_pattern ||= /^test_/
end end
def total_tests
self.subclasses.inject(0){|total, k| total + k.testmethods.size }
end
end
end
class ThePath
def walk
sensei = EdgeCase::Sensei.new
each_step do |step|
sensei.observe(step.meditate)
end
sensei.instruct
end
def each_step
catch(:edgecase_exit) {
step_count = 0
EdgeCase::Koan.subclasses.each_with_index do |koan,koan_index|
koan.testmethods.each do |method_name|
step = koan.new(method_name, koan.to_s, koan_index+1, step_count+=1)
yield step
end
end
}
end end
end end
end end
END { END {
EdgeCase::Koan.command_line(ARGV) EdgeCase::Koan.command_line(ARGV)
zen_master = EdgeCase::Sensei.new EdgeCase::ThePath.new.walk
catch(:edgecase_exit) {
EdgeCase::Koan.subclasses.each do |sc|
sc.run_tests(zen_master)
end
}
zen_master.report
} }