20 Commits

Author SHA1 Message Date
Jim Weirich
584b26e532 Updated for JRuby 2010-09-27 10:44:58 -04:00
Jim Weirich
b2c47e0c0f Switch blue color to cyan for better contrast on dark terminals. 2010-09-27 10:37:35 -04:00
Marc Peabody
f56117c0ca merge and 80 char limit on src 2010-09-22 15:14:11 -04:00
Marc Peabody
15551eaf53 80 char limit to end screen 2010-09-22 15:09:08 -04:00
Jim Weirich
b4e907e30e Updated Koans directory 2010-09-22 14:54:42 -04:00
Jim Weirich
754a7694ad Added else clause if progress file is not there. 2010-09-22 14:50:25 -04:00
Marc Peabody
1492d7003a regen for updated koans/edgecase.rb 2010-09-22 14:10:34 -04:00
Marc Peabody
493300b24d end screen with koans logo ascii art 2010-09-22 14:08:38 -04:00
Matt Yoho
3ed32b4534 Use correct method for exception fill-in blank 2010-09-21 16:05:02 -04:00
Matt Yoho
aa09d17630 Clean up about_koans and add another example 2010-09-21 16:04:58 -04:00
Daniel Parker
a36c3b7a4d Fixed typo 2010-09-21 17:41:09 +01:00
Matt Yoho
18cccf33fb Add about_symbols koan 2010-09-21 12:03:31 -04:00
Erik Ogan
1597b2f912 minor grammar nit 2010-09-19 16:53:17 -07:00
Jim Weirich
57f0f4f178 Updated koans from source. 2010-09-12 21:33:11 -04:00
Jim Weirich
0794235441 Removed utf-8 character in comments. 2010-09-12 21:32:55 -04:00
Jim Weirich
ad99c372c3 Normalized file name and koan category name. 2010-09-12 21:27:22 -04:00
Jim Weirich
8d8287365b Added bonus question about the file read. 2010-09-12 21:27:01 -04:00
Jim Weirich
0c18e9742f Simplified file read example. 2010-09-12 21:26:49 -04:00
Jim Weirich
28eec8cc41 Ignoring project env rc file. 2010-09-12 21:25:42 -04:00
Greg Mefford
b0e34a90e0 Cleaned up some messy File housekeeping. 2010-09-12 17:07:22 -04:00
24 changed files with 633 additions and 161 deletions

2
.gitignore vendored
View File

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

View File

@@ -60,7 +60,7 @@ class AboutBlocks < EdgeCase::Koan
# ------------------------------------------------------------------ # ------------------------------------------------------------------
def test_block_can_effect_variables_in_the_code_where_they_are_created def test_block_can_affect_variables_in_the_code_where_they_are_created
value = :initial_value value = :initial_value
method_with_block { value = :modified_in_a_block } method_with_block { value = :modified_in_a_block }
assert_equal __, value assert_equal __, value

View File

@@ -7,7 +7,7 @@ class DiceSet
end end
end end
class AboutDiceSet < EdgeCase::Koan class AboutDiceProject < EdgeCase::Koan
def test_can_create_a_dice_set def test_can_create_a_dice_set
dice = DiceSet.new dice = DiceSet.new
assert_not_nil dice assert_not_nil dice

View File

@@ -78,16 +78,26 @@ class AboutIteration < EdgeCase::Koan
assert_equal __, result assert_equal __, result
# Files act like a collection of lines # Files act like a collection of lines
file = File.open("example_file.txt") File.open("example_file.txt") do |file|
upcase_lines = file.map { |line| line.strip.upcase } upcase_lines = file.map { |line| line.strip.upcase }
assert_equal __, upcase_lines assert_equal __, upcase_lines
end
# NOTE: You can create your own collections that work with each, # NOTE: You can create your own collections that work with each,
# map, select, etc. # map, select, etc.
ensure
# Arg, this is ugly.
# We will figure out how to fix this later.
file.close if file
end end
# Bonus Question: In the previous koan, we saw the construct:
#
# File.open(filename) do |file|
# # code to read 'file'
# end
#
# Why did we do it that way instead of the following?
#
# file = File.open(filename)
# # code to read 'file'
#
# When you get to the "AboutSandwichCode" koan, recheck your answer.
end end

View File

@@ -141,7 +141,7 @@ class AboutRegularExpressions < EdgeCase::Koan
# THINK ABOUT IT: # THINK ABOUT IT:
# #
# Explain the difference between a character class ([]) and alternation (|). # Explain the difference between a character class ([...]) and alternation (|).
# ------------------------------------------------------------------ # ------------------------------------------------------------------

View File

@@ -1,6 +1,6 @@
require File.expand_path(File.dirname(__FILE__) + '/edgecase') require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutUsingBlocks < EdgeCase::Koan class AboutSandwichCode < EdgeCase::Koan
def count_lines(file_name) def count_lines(file_name)
file = open(file_name) file = open(file_name)

View File

@@ -33,7 +33,7 @@ def score(dice)
# You need to write this method # You need to write this method
end end
class AboutScoringAssignment < EdgeCase::Koan class AboutScoringProject < EdgeCase::Koan
def test_score_of_an_empty_list_is_zero def test_score_of_an_empty_list_is_zero
assert_equal 0, score([]) assert_equal 0, score([])
end end

77
koans/about_symbols.rb Normal file
View File

@@ -0,0 +1,77 @@
require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutSymbols < EdgeCase::Koan
def test_symbols_are_symbols
symbol = :ruby
assert_equal __, symbol.is_a?(Symbol)
end
def test_symbols_can_be_compared
symbol1 = :a_symbol
symbol2 = :a_symbol
symbol3 = :something_else
assert symbol1 == __
assert symbol1 != __
end
def test_identical_symbols_are_a_single_internal_object
symbol1 = :a_symbol
symbol2 = :a_symbol
assert symbol1.equal?(__)
assert_equal __, symbol2.object_id
end
def test_method_names_become_symbols
all_symbols = Symbol.all_symbols
assert_equal __, all_symbols.include?(:test_method_names_are_symbols)
end
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?(__)
end
def test_symbols_can_be_made_from_strings
string = "catsAndDogs"
assert_equal __, string.to_sym
end
def test_symbols_with_spaces_can_be_built
symbol = :"cats and dogs"
assert_equal symbol, __.to_sym
end
def test_to_s_is_called_on_interpolated_symbols
symbol = :cats
string = "It is raining #{symbol} and dogs."
assert_equal __, string
end
def test_symbols_are_not_strings
symbol = :ruby
assert_equal __, symbol.is_a?(String)
assert_equal __, symbol.eql?("ruby")
end
def test_symbols_do_not_have_string_methods
symbol = :not_a_string
assert_equal __, symbol.respond_to?(:each_char)
assert_equal __, symbol.respond_to?(:reverse)
end
# It's important to realize that symbols are not "immutable
# strings", though they are immutable. None of the
# interesting string operations are available on symbols.
def test_symbols_cannot_be_concatenated
# Exceptions will be pondered further father down the path
assert_raise(___) do
:cats + :dogs
end
end
end

View File

@@ -3,7 +3,7 @@ require File.expand_path(File.dirname(__FILE__) + '/edgecase')
# You need to write the triangle method in the file 'triangle.rb' # You need to write the triangle method in the file 'triangle.rb'
require 'triangle.rb' require 'triangle.rb'
class AboutTriangleAssignment < EdgeCase::Koan class AboutTriangleProject < EdgeCase::Koan
def test_equilateral_triangles_have_equal_sides def test_equilateral_triangles_have_equal_sides
assert_equal :equilateral, triangle(2, 2, 2) assert_equal :equilateral, triangle(2, 2, 2)
assert_equal :equilateral, triangle(10, 10, 10) assert_equal :equilateral, triangle(10, 10, 10)

View File

@@ -3,7 +3,7 @@ require File.expand_path(File.dirname(__FILE__) + '/edgecase')
# You need to write the triangle method in the file 'triangle.rb' # You need to write the triangle method in the file 'triangle.rb'
require 'triangle.rb' require 'triangle.rb'
class AboutTriangleAssignment2 < EdgeCase::Koan class AboutTriangleProject2 < EdgeCase::Koan
# The first assignment did not talk about how to handle errors. # The first assignment did not talk about how to handle errors.
# Let's handle that part now. # Let's handle that part now.
def test_illegal_triangles_throw_exceptions def test_illegal_triangles_throw_exceptions

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?
@pass_count += 1 def add_progress(prog)
puts Color.green(" #{test.name} has expanded your awareness.") @_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 else
puts Color.red(" #{test.name} has damaged your karma.") @_contents = []
@failed_test = test end
@failure = test.failure end
@_contents
end
def observe(step)
if step.passed?
@pass_count += 1
if @pass_count > progress.last.to_i
@observations << Color.green("#{step.koan_file}##{step.name} has expanded your awareness.")
end
else
@failed_test = step
@failure = step.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?
#puts find_interesting_lines(failure.backtrace)
puts find_interesting_lines(failure.backtrace).collect {|l| Color.red(l) }
else else
puts Color.red(failure.backtrace) end_screen
end
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 end
puts puts
end end
puts Color.green(a_zenlike_statement)
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

@@ -8,6 +8,7 @@ require 'about_arrays'
require 'about_array_assignment' require 'about_array_assignment'
require 'about_hashes' require 'about_hashes'
require 'about_strings' require 'about_strings'
require 'about_symbols'
require 'about_regular_expressions' require 'about_regular_expressions'
require 'about_methods' require 'about_methods'
require 'about_constants' require 'about_constants'

View File

@@ -60,7 +60,7 @@ class AboutBlocks < EdgeCase::Koan
# ------------------------------------------------------------------ # ------------------------------------------------------------------
def test_block_can_effect_variables_in_the_code_where_they_are_created def test_block_can_affect_variables_in_the_code_where_they_are_created
value = :initial_value value = :initial_value
method_with_block { value = :modified_in_a_block } method_with_block { value = :modified_in_a_block }
assert_equal __(:modified_in_a_block), value assert_equal __(:modified_in_a_block), value

View File

@@ -7,7 +7,7 @@ class DiceSet
end end
end end
class AboutDiceSet < EdgeCase::Koan class AboutDiceProject < EdgeCase::Koan
def test_can_create_a_dice_set def test_can_create_a_dice_set
dice = DiceSet.new dice = DiceSet.new
assert_not_nil dice assert_not_nil dice

View File

@@ -78,16 +78,26 @@ class AboutIteration < EdgeCase::Koan
assert_equal __([11, 12, 13]), result assert_equal __([11, 12, 13]), result
# Files act like a collection of lines # Files act like a collection of lines
file = File.open("example_file.txt") File.open("example_file.txt") do |file|
upcase_lines = file.map { |line| line.strip.upcase } upcase_lines = file.map { |line| line.strip.upcase }
assert_equal __(["THIS", "IS", "A", "TEST"]), upcase_lines assert_equal __(["THIS", "IS", "A", "TEST"]), upcase_lines
end
# NOTE: You can create your own collections that work with each, # NOTE: You can create your own collections that work with each,
# map, select, etc. # map, select, etc.
ensure
# Arg, this is ugly.
# We will figure out how to fix this later.
file.close if file
end end
# Bonus Question: In the previous koan, we saw the construct:
#
# File.open(filename) do |file|
# # code to read 'file'
# end
#
# Why did we do it that way instead of the following?
#
# file = File.open(filename)
# # code to read 'file'
#
# When you get to the "AboutSandwichCode" koan, recheck your answer.
end end

View File

@@ -43,12 +43,12 @@ class AboutMethods < EdgeCase::Koan
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
# ------------------------------------------------------------------ # ------------------------------------------------------------------

View File

@@ -141,7 +141,7 @@ class AboutRegularExpressions < EdgeCase::Koan
# THINK ABOUT IT: # THINK ABOUT IT:
# #
# Explain the difference between a character class ([]) and alternation (|). # Explain the difference between a character class ([...]) and alternation (|).
# ------------------------------------------------------------------ # ------------------------------------------------------------------

View File

@@ -1,6 +1,6 @@
require File.expand_path(File.dirname(__FILE__) + '/edgecase') require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutUsingBlocks < EdgeCase::Koan class AboutSandwichCode < EdgeCase::Koan
def count_lines(file_name) def count_lines(file_name)
file = open(file_name) file = open(file_name)

View File

@@ -54,7 +54,7 @@ def score(dice)
#++ #++
end end
class AboutScoringAssignment < EdgeCase::Koan class AboutScoringProject < EdgeCase::Koan
def test_score_of_an_empty_list_is_zero def test_score_of_an_empty_list_is_zero
assert_equal 0, score([]) assert_equal 0, score([])
end end

100
src/about_symbols.rb Normal file
View File

@@ -0,0 +1,100 @@
require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutSymbols < EdgeCase::Koan
def test_symbols_are_symbols
symbol = :ruby
assert_equal __(true), symbol.is_a?(Symbol)
end
def test_symbols_can_be_compared
symbol1 = :a_symbol
symbol2 = :a_symbol
symbol3 = :something_else
assert symbol1 == __(symbol2)
assert symbol1 != __(symbol3)
end
def test_identical_symbols_are_a_single_internal_object
symbol1 = :a_symbol
symbol2 = :a_symbol
assert symbol1.equal?(__(symbol2))
assert_equal __(symbol1.object_id), symbol2.object_id
end
def test_method_names_become_symbols
all_symbols = Symbol.all_symbols
assert_equal __(true), all_symbols.include?(:test_method_names_become_symbols)
end
# THINK ABOUT IT:
#
# Why do we capture the list of symbols before we check for the
# method name?
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
def test_symbols_can_be_made_from_strings
string = "catsAndDogs"
assert_equal __(:catsAndDogs), string.to_sym
end
def test_symbols_with_spaces_can_be_built
symbol = :"cats and dogs"
assert_equal symbol, __("cats and dogs").to_sym
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
symbol = :cats
string = "It is raining #{symbol} and dogs."
assert_equal __('It is raining cats and dogs.'), string
end
def test_symbols_are_not_strings
symbol = :ruby
assert_equal __(false), symbol.is_a?(String)
assert_equal __(false), symbol.eql?("ruby")
end
def test_symbols_do_not_have_string_methods
symbol = :not_a_string
assert_equal __(false), symbol.respond_to?(:each_char)
assert_equal __(false), symbol.respond_to?(:reverse)
end
# It's important to realize that symbols are not "immutable
# strings", though they are immutable. None of the
# interesting string operations are available on symbols.
def test_symbols_cannot_be_concatenated
# Exceptions will be pondered further father down the path
assert_raise(___(NoMethodError)) do
:cats + :dogs
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

View File

@@ -3,7 +3,7 @@ require File.expand_path(File.dirname(__FILE__) + '/edgecase')
# You need to write the triangle method in the file 'triangle.rb' # You need to write the triangle method in the file 'triangle.rb'
require 'triangle.rb' require 'triangle.rb'
class AboutTriangleAssignment < EdgeCase::Koan class AboutTriangleProject < EdgeCase::Koan
def test_equilateral_triangles_have_equal_sides def test_equilateral_triangles_have_equal_sides
assert_equal :equilateral, triangle(2, 2, 2) assert_equal :equilateral, triangle(2, 2, 2)
assert_equal :equilateral, triangle(10, 10, 10) assert_equal :equilateral, triangle(10, 10, 10)

View File

@@ -3,7 +3,7 @@ require File.expand_path(File.dirname(__FILE__) + '/edgecase')
# You need to write the triangle method in the file 'triangle.rb' # You need to write the triangle method in the file 'triangle.rb'
require 'triangle.rb' require 'triangle.rb'
class AboutTriangleAssignment2 < EdgeCase::Koan class AboutTriangleProject2 < EdgeCase::Koan
# The first assignment did not talk about how to handle errors. # The first assignment did not talk about how to handle errors.
# Let's handle that part now. # Let's handle that part now.
def test_illegal_triangles_throw_exceptions def test_illegal_triangles_throw_exceptions

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?
@pass_count += 1 def add_progress(prog)
puts Color.green(" #{test.name} has expanded your awareness.") @_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 else
puts Color.red(" #{test.name} has damaged your karma.") @_contents = []
@failed_test = test end
@failure = test.failure end
@_contents
end
def observe(step)
if step.passed?
@pass_count += 1
if @pass_count > progress.last.to_i
@observations << Color.green("#{step.koan_file}##{step.name} has expanded your awareness.")
end
else
@failed_test = step
@failure = step.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?
#puts find_interesting_lines(failure.backtrace)
puts find_interesting_lines(failure.backtrace).collect {|l| Color.red(l) }
else else
puts Color.red(failure.backtrace) end_screen
end
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 end
puts puts
end end
puts Color.green(a_zenlike_statement)
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
} }

View File

@@ -8,6 +8,7 @@ require 'about_arrays'
require 'about_array_assignment' require 'about_array_assignment'
require 'about_hashes' require 'about_hashes'
require 'about_strings' require 'about_strings'
require 'about_symbols'
require 'about_regular_expressions' require 'about_regular_expressions'
require 'about_methods' require 'about_methods'
require 'about_constants' require 'about_constants'