63 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
Jim Weirich
b0a01786f4 Updated koan directory from src. 2010-08-27 09:44:10 -04:00
Rene Mendoza
ed2ef0f439 Added autotest support to RubyKoans 2010-08-27 09:38:09 -04:00
Rene Mendoza
1f6c0e03a5 Color support for Ruby Koans 2010-08-27 09:38:09 -04:00
Rene Mendoza
06c47eb284 Added autotest support and added colorized output of tests 2010-08-27 09:37:33 -04:00
Jim Weirich
bc1a20a310 Tweeks on the color module 2010-08-27 09:35:33 -04:00
Jim Weirich
e6fc8f6434 Renamed method 2010-08-27 09:35:33 -04:00
Jim Weirich
36f93c6c63 Updated koans with changed from src. 2010-08-26 07:58:28 -04:00
Jim Weirich
bc55853983 Updated comment 2010-08-26 07:57:28 -04:00
Jim Weirich
d17cc7b457 Disable colorization when the NO_COLOR env variable is defined. 2010-08-26 07:55:04 -04:00
Jim Weirich
f88e9f1dfc Removed method missing from color module. 2010-08-26 07:53:22 -04:00
Jim Weirich
05d5ab9a5e Added colors to output (based on renemendoza's patch). 2010-08-26 07:45:31 -04:00
Jim Weirich
1b31a5b4dc Updated koans from new regex section. 2010-08-22 02:03:17 -04:00
Jim Weirich
3aa75e86aa Fixed spelling of repetition. 2010-08-22 01:55:02 -04:00
Jim Weirich
9c0ccd53a2 Merge branch 'master' of git://github.com/JEG2/ruby_koans into numeric
* 'master' of git://github.com/JEG2/ruby_koans:
  Adding Regular Expression koans.
  Fixing a bug that was causing some number Koans to be presolved.
2010-08-22 01:36:28 -04:00
James Edward Gray II
9a459a7eb3 Adding Regular Expression koans. 2010-08-21 22:11:06 -05:00
James Edward Gray II
e1ada6ca9f Fixing a bug that was causing some number Koans to be presolved. 2010-08-21 15:34:54 -05:00
Jonathan Castello
a5180d9ea1 Typo fix: changed "parenthesis" to "parentheses" 2010-08-21 11:32:57 -07:00
Jim Weirich
7db19b5e92 White space fixes 2010-08-21 09:01:10 -04:00
Jim Weirich
aa21cae64b Merge branch 'master' of http://github.com/Twisol/ruby_koans into twisol
* 'master' of http://github.com/Twisol/ruby_koans:
  With 'test_in_ruby_1_8_single_characters_are_represented_by_strings', changed the name to use '1.9', since it's in an in_ruby_version("1.9") block. (Confused me for a good while.)
2010-08-21 08:55:49 -04:00
Jonathan Castello
7c52931392 With 'test_in_ruby_1_8_single_characters_are_represented_by_strings', changed the name to use '1.9', since it's in an in_ruby_version("1.9") block. (Confused me for a good while.) 2010-08-21 01:33:13 -07:00
Jim Weirich
028e4a0c75 Updated README (tweeks words, whitespace cleanup) 2010-08-17 08:41:18 -04:00
Jim Weirich
eee3429394 Updated the koans directory. 2010-08-16 16:57:13 -04:00
Jim Weirich
6365c8d61d Now supporting Test::Unit as well under Ruby 1.9. 2010-08-16 16:56:42 -04:00
Jim Weirich
f2ac27f52e Added support for minitest under Ruby 1.9. 2010-08-16 16:53:56 -04:00
Jim Weirich
c4549eb24b Make method_missing public in the test framework for Ruby 1.9. 2010-08-16 15:40:54 -04:00
Jim Weirich
ee9a03740b Setup load path in path_to_enlightenment, so -I not needed in Rakefile. 2010-08-16 15:39:46 -04:00
Jim Weirich
8ef93f1d3a UPdated koans to match latest source. 2010-08-16 15:06:12 -04:00
Jim Weirich
7046294343 Answers now Ruby 1.9 compliant 2010-08-16 15:05:20 -04:00
Jim Weirich
e8456fa321 Added current directory to rake runner. 2010-08-16 15:03:44 -04:00
Jim Weirich
138bc7c9bc Minor change to comment about regular expressions. 2010-08-16 14:59:04 -04:00
Jim Weirich
2c3b65ec79 Updated require with yucky File.dirname(__FILE__) hacks. 2010-08-16 14:40:37 -04:00
Jim Weirich
fbd36f6b0d Reworded question. 2010-08-16 13:59:58 -04:00
Jim Weirich
56206e0a41 Removed unneeded check for no args. 2010-04-20 16:26:57 -04:00
Jim Weirich
308ad5d9c5 Added constants to koans path to enlightenment. 2010-04-20 16:26:18 -04:00
Jim Weirich
c40424e9a4 Updates source to be consistent with the _n_ changes. 2010-04-20 16:25:19 -04:00
Jim Weirich
f0b1560f34 Added about_constants 2010-04-20 16:17:34 -04:00
Jim Weirich
3b824d0e86 Modified to be less dependent on exact method and constant counts. 2010-04-20 16:17:24 -04:00
Jim Weirich
110ff38b3f Added a numeric fill in method (_n_) 2010-04-20 16:16:55 -04:00
Jim Weirich
1d1ab85777 Generated new koans directory. 2010-01-15 09:53:28 -05:00
Jim Weirich
d4c9150453 Added swap example to parallel assignments 2010-01-15 09:51:50 -05:00
Jim Weirich
ec6458e174 Renamed if test to avoid method name conflict. 2010-01-15 09:46:17 -05:00
Jim Weirich
6c69bcb0ef Added [1,1,3] case for triangles. 2010-01-15 09:39:48 -05:00
Jim Weirich
6052c776bc Use .values rather than .keys in values test (http://www.pivotaltracker.com/story/show/2177802) 2010-01-14 14:31:30 -05:00
70 changed files with 1577 additions and 370 deletions

2
.gitignore vendored
View File

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

View File

@@ -21,6 +21,7 @@ module Koans
line = line.gsub(/\b____\([^\)]+\)/, "____") line = line.gsub(/\b____\([^\)]+\)/, "____")
line = line.gsub(/\b___\([^\)]+\)/, "___") line = line.gsub(/\b___\([^\)]+\)/, "___")
line = line.gsub(/\b__\([^\)]+\)/, "__") line = line.gsub(/\b__\([^\)]+\)/, "__")
line = line.gsub(/\b_n_\([^\)]+\)/, "_n_")
line = line.gsub(%r(/\#\{__\}/), "/__/") line = line.gsub(%r(/\#\{__\}/), "/__/")
line line
end end
@@ -28,6 +29,8 @@ module Koans
def Koans.make_koan_file(infile, outfile) def Koans.make_koan_file(infile, outfile)
if infile =~ /edgecase/ if infile =~ /edgecase/
cp infile, outfile cp infile, outfile
elsif infile =~ /autotest/
cp_r infile, outfile
else else
open(infile) do |ins| open(infile) do |ins|
open(outfile, "w") do |outs| open(outfile, "w") do |outs|

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutArrayAssignment < EdgeCase::Koan class AboutArrayAssignment < EdgeCase::Koan
def test_non_parallel_assignment def test_non_parallel_assignment
@@ -35,4 +35,11 @@ class AboutArrayAssignment < EdgeCase::Koan
assert_equal __, first_name assert_equal __, first_name
end end
def test_swapping_with_parallel_assignment
first_name = "Roy"
last_name = "Rob"
first_name, last_name = last_name, first_name
assert_equal __, first_name
assert_equal __, last_name
end
end end

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutArrays < EdgeCase::Koan class AboutArrays < EdgeCase::Koan
def test_creating_arrays def test_creating_arrays

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
# -*- ruby -*- # -*- ruby -*-
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutAsserts < EdgeCase::Koan class AboutAsserts < EdgeCase::Koan

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutBlocks < EdgeCase::Koan class AboutBlocks < EdgeCase::Koan
def method_with_block def method_with_block
@@ -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

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutClassMethods < EdgeCase::Koan class AboutClassMethods < EdgeCase::Koan
class Dog class Dog
@@ -19,11 +19,11 @@ class AboutClassMethods < EdgeCase::Koan
def test_objects_have_methods def test_objects_have_methods
fido = Dog.new fido = Dog.new
assert_equal __, fido.methods.size assert fido.methods.size > _n_
end end
def test_classes_have_methods def test_classes_have_methods
assert_equal __, Dog.methods.size assert Dog.methods.size > _n_
end end
def test_you_can_define_methods_on_individual_objects def test_you_can_define_methods_on_individual_objects

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutClasses < EdgeCase::Koan class AboutClasses < EdgeCase::Koan
class Dog class Dog

87
koans/about_constants.rb Normal file
View File

@@ -0,0 +1,87 @@
require File.expand_path(File.dirname(__FILE__) + '/edgecase')
C = "top level"
class AboutConstants < EdgeCase::Koan
C = "nested"
def test_nested_constants_may_also_be_referenced_with_relative_paths
assert_equal __, C
end
def test_top_level_constants_are_referenced_by_double_colons
assert_equal __, ::C
end
def test_nested_constants_are_referenced_by_their_complete_path
assert_equal __, AboutConstants::C
assert_equal __, ::AboutConstants::C
end
# ------------------------------------------------------------------
class Animal
LEGS = 4
def legs_in_animal
LEGS
end
class NestedAnimal
def legs_in_nested_animal
LEGS
end
end
end
def test_nested_classes_inherit_constants_from_enclosing_classes
assert_equal __, Animal::NestedAnimal.new.legs_in_nested_animal
end
# ------------------------------------------------------------------
class Reptile < Animal
def legs_in_reptile
LEGS
end
end
def test_subclasses_inherit_constants_from_parent_classes
assert_equal __, Reptile.new.legs_in_reptile
end
# ------------------------------------------------------------------
class MyAnimals
LEGS = 2
class Bird < Animal
def legs_in_bird
LEGS
end
end
end
def test_who_wins_with_both_nested_and_inherited_constants
assert_equal __, MyAnimals::Bird.new.legs_in_bird
end
# QUESTION: Which has precedence: The constant in the lexical scope,
# or the constant from the inheritance heirarachy?
# ------------------------------------------------------------------
class MyAnimals::Oyster < Animal
def legs_in_oyster
LEGS
end
end
def test_who_wins_with_explicit_scoping_on_class_definition
assert_equal __, MyAnimals::Oyster.new.legs_in_oyster
end
# QUESTION: Now Which has precedence: The constant in the lexical
# scope, or the constant from the inheritance heirarachy? Why is it
# different than the previous answer?
end

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutControlStatements < EdgeCase::Koan class AboutControlStatements < EdgeCase::Koan
@@ -11,7 +11,7 @@ class AboutControlStatements < EdgeCase::Koan
assert_equal __, result assert_equal __, result
end end
def test_if_then_else_statements def test_if_then_statements
result = :default_value result = :default_value
if true if true
result = :true_value result = :true_value

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class DiceSet class DiceSet
attr_reader :values attr_reader :values
@@ -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

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutExceptions < EdgeCase::Koan class AboutExceptions < EdgeCase::Koan

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutHashes < EdgeCase::Koan class AboutHashes < EdgeCase::Koan
def test_creating_hashes def test_creating_hashes
@@ -48,7 +48,7 @@ class AboutHashes < EdgeCase::Koan
def test_hash_values def test_hash_values
hash = { :one => "uno", :two => "dos" } hash = { :one => "uno", :two => "dos" }
assert_equal __, hash.keys.size assert_equal __, hash.values.size
assert_equal __, hash.values.include?("uno") assert_equal __, hash.values.include?("uno")
assert_equal __, hash.values.include?("dos") assert_equal __, hash.values.include?("dos")
assert_equal Array, hash.values.class assert_equal Array, hash.values.class

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutInheritance < EdgeCase::Koan class AboutInheritance < EdgeCase::Koan
class Dog class Dog

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutIteration < EdgeCase::Koan class AboutIteration < EdgeCase::Koan
@@ -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

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutMessagePassing < EdgeCase::Koan class AboutMessagePassing < EdgeCase::Koan
@@ -49,7 +49,6 @@ class AboutMessagePassing < EdgeCase::Koan
class MessageCatcher class MessageCatcher
def add_a_payload(*args) def add_a_payload(*args)
return :empty unless args
args args
end end
end end
@@ -90,6 +89,18 @@ class AboutMessagePassing < EdgeCase::Koan
# #
# If the method :method_missing causes the NoMethodError, then # If the method :method_missing causes the NoMethodError, then
# what would happen if we redefine method_missing? # what would happen if we redefine method_missing?
#
# NOTE:
#
# In Ruby 1.8 the method_missing method is public and can be
# called as shown above. However, in Ruby 1.9 the method_missing
# method is private. We explicitly made it public in the testing
# framework so this example works in both versions of Ruby. Just
# keep in mind you can't call method_missing like that in Ruby
# 1.9. normally.
#
# Thanks. We now return you to your regularly schedule Ruby
# Koans.
end end
# ------------------------------------------------------------------ # ------------------------------------------------------------------

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
def my_global_method(a,b) def my_global_method(a,b)
a + b a + b
@@ -10,14 +10,14 @@ class AboutMethods < EdgeCase::Koan
assert_equal __, my_global_method(2,3) assert_equal __, my_global_method(2,3)
end end
def test_calling_global_methods_without_parenthesis def test_calling_global_methods_without_parentheses
result = my_global_method 2, 3 result = my_global_method 2, 3
assert_equal __, result assert_equal __, result
end end
# (NOTE: We are Using eval below because the example code is # (NOTE: We are Using eval below because the example code is
# considered to be syntactically invalid). # considered to be syntactically invalid).
def test_sometimes_missing_parenthesis_are_ambiguous def test_sometimes_missing_parentheses_are_ambiguous
eval "assert_equal 5, my_global_method 2, 3" # ENABLE CHECK eval "assert_equal 5, my_global_method 2, 3" # ENABLE CHECK
# #
# Ruby doesn't know if you mean: # Ruby doesn't know if you mean:

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutModules < EdgeCase::Koan class AboutModules < EdgeCase::Koan
module Nameable module Nameable

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutNil < EdgeCase::Koan class AboutNil < EdgeCase::Koan
def test_nil_is_an_object def test_nil_is_an_object

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutOpenClasses < EdgeCase::Koan class AboutOpenClasses < EdgeCase::Koan
class Dog class Dog

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
# Project: Create a Proxy Class # Project: Create a Proxy Class
# #

View File

@@ -0,0 +1,159 @@
# -*- coding: utf-8 -*-
require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutRegularExpressions < EdgeCase::Koan
def test_a_pattern_is_a_regular_expression
assert_equal Regexp, /pattern/.class
end
def test_a_regexp_can_search_a_string_for_matching_content
assert_equal "match", "some matching content"[/match/]
end
def test_a_failed_match_returns_nil
assert_equal __, "some matching content"[/missing/]
end
# ------------------------------------------------------------------
def test_question_mark_means_optional
assert_equal __, "abbcccddddeeeee"[/ab?/]
assert_equal __, "abbcccddddeeeee"[/az?/]
end
def test_plus_means_one_or_more
assert_equal __, "abbcccddddeeeee"[/bc+/]
end
def test_asterisk_means_zero_or_more
assert_equal __, "abbcccddddeeeee"[/ab*/]
assert_equal __, "abbcccddddeeeee"[/az*/]
assert_equal __, "abbcccddddeeeee"[/z*/]
# THINK ABOUT IT:
#
# When would * fail to match?
end
# THINK ABOUT IT:
#
# We say that the repetition operators above are "greedy."
#
# Why?
# ------------------------------------------------------------------
def test_the_left_most_match_wins
assert_equal __, "abbccc az"[/az*/]
end
# ------------------------------------------------------------------
def test_character_classes_give_options_for_a_character
animals = ["cat", "bat", "rat", "zat"]
assert_equal __, animals.select { |a| a[/[cbr]at/] }
end
def test_slash_d_is_a_shortcut_for_a_digit_character_class
assert_equal __, "the number is 42"[/[0123456789]+/]
assert_equal __, "the number is 42"[/\d+/]
end
def test_character_classes_can_include_ranges
assert_equal __, "the number is 42"[/[0-9]+/]
end
def test_slash_s_is_a_shortcut_for_a_whitespace_character_class
assert_equal __, "space: \t\n"[/\s+/]
end
def test_slash_w_is_a_shortcut_for_a_word_character_class
# NOTE: This is more like how a programmer might define a word.
assert_equal __, "variable_1 = 42"[/[a-zA-Z0-9_]+/]
assert_equal __, "variable_1 = 42"[/\w+/]
end
def test_period_is_a_shortcut_for_any_non_newline_character
assert_equal __, "abc\n123"[/a.+/]
end
def test_a_character_class_can_be_negated
assert_equal __, "the number is 42"[/[^0-9]+/]
end
def test_shortcut_character_classes_are_negated_with_capitals
assert_equal __, "the number is 42"[/\D+/]
assert_equal __, "space: \t\n"[/\S+/]
assert_equal __, "variable_1 = 42"[/\W+/]
end
# ------------------------------------------------------------------
def test_slash_a_anchors_to_the_start_of_the_string
assert_equal __, "start end"[/\Astart/]
assert_equal __, "start end"[/\Aend/]
end
def test_slash_z_anchors_to_the_end_of_the_string
assert_equal __, "start end"[/end\z/]
assert_equal __, "start end"[/start\z/]
end
def test_caret_anchors_to_the_start_of_lines
assert_equal __, "num 42\n2 lines"[/^\d+/]
end
def test_dollar_sign_anchors_to_the_end_of_lines
assert_equal __, "2 lines\nnum 42"[/\d+$/]
end
def test_slash_b_anchors_to_a_word_boundary
assert_equal __, "bovine vines"[/\bvine./]
end
# ------------------------------------------------------------------
def test_parentheses_group_contents
assert_equal __, "ahahaha"[/(ha)+/]
end
# ------------------------------------------------------------------
def test_parentheses_also_capture_matched_content_by_number
assert_equal __, "Gray, James"[/(\w+), (\w+)/, 1]
assert_equal __, "Gray, James"[/(\w+), (\w+)/, 2]
end
def test_variables_can_also_be_used_to_access_captures
assert_equal __, "Name: Gray, James"[/(\w+), (\w+)/]
assert_equal __, $1
assert_equal __, $2
end
# ------------------------------------------------------------------
def test_a_vertical_pipe_means_or
grays = /(James|Dana|Summer) Gray/
assert_equal __, "James Gray"[grays]
assert_equal __, "Summer Gray"[grays, 1]
assert_equal __, "Jim Gray"[grays, 1]
end
# THINK ABOUT IT:
#
# Explain the difference between a character class ([...]) and alternation (|).
# ------------------------------------------------------------------
def test_scan_is_like_find_all
assert_equal __, "one two-three".scan(/\w+/)
end
def test_sub_is_like_find_and_replace
assert_equal __, "one two-three".sub(/(t\w*)/) { $1[0, 1] }
end
def test_gsub_is_like_find_and_replace_all
assert_equal __, "one two-three".gsub(/(t\w*)/) { $1[0, 1] }
end
end

View File

@@ -1,6 +1,6 @@
require '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

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutScope < EdgeCase::Koan class AboutScope < EdgeCase::Koan
module Jims module Jims
@@ -74,6 +74,6 @@ class AboutScope < EdgeCase::Koan
def test_you_can_get_a_list_of_constants_for_any_class_or_module def test_you_can_get_a_list_of_constants_for_any_class_or_module
assert_equal __, Jims.constants assert_equal __, Jims.constants
assert_equal __, Object.constants.size assert Object.constants.size > _n_
end end
end end

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
# Greed is a dice game where you roll up to five dice to accumulate # Greed is a dice game where you roll up to five dice to accumulate
# points. The following "score" function will be used calculate the # points. The following "score" function will be used calculate the
@@ -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

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutStrings < EdgeCase::Koan class AboutStrings < EdgeCase::Koan
def test_double_quoted_strings_are_strings def test_double_quoted_strings_are_strings
@@ -146,11 +146,20 @@ EOS
# Surprised? # Surprised?
end end
def test_single_characters_are_represented_by_integers in_ruby_version("1.8") do
assert_equal __, ?a def test_in_ruby_1_8_single_characters_are_represented_by_integers
assert_equal __, ?a == 97 assert_equal __, ?a
assert_equal __, ?a == 97
assert_equal __, ?b == (?a + 1) assert_equal __, ?b == (?a + 1)
end
end
in_ruby_version("1.9") do
def test_in_ruby_1_9_single_characters_are_represented_by_strings
assert_equal __, ?a
assert_equal __, ?a == 97
end
end end
def test_strings_can_be_split def test_strings_can_be_split
@@ -165,8 +174,8 @@ EOS
assert_equal [__, __, __, __], words assert_equal [__, __, __, __], words
# NOTE: Patterns are formed from Regular Expressions. Ruby has a # NOTE: Patterns are formed from Regular Expressions. Ruby has a
# very powerful Regular Expression library. Unfortunately, time # very powerful Regular Expression library. We will become
# does not permit us to explore it in detail in Ruby 101. # enlightened about them soon.
end end
def test_strings_can_be_joined def test_strings_can_be_joined

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

@@ -1,9 +1,9 @@
require 'edgecase' 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

@@ -1,14 +1,15 @@
require 'edgecase' 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
assert_raise(TriangleError) do triangle(0, 0, 0) end assert_raise(TriangleError) do triangle(0, 0, 0) end
assert_raise(TriangleError) do triangle(3, 4, -5) end assert_raise(TriangleError) do triangle(3, 4, -5) end
assert_raise(TriangleError) do triangle(1, 1, 3) end
assert_raise(TriangleError) do triangle(2, 4, 2) end assert_raise(TriangleError) do triangle(2, 4, 2) end
end end
end end

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutTrueAndFalse < EdgeCase::Koan class AboutTrueAndFalse < EdgeCase::Koan
def truth_value(condition) def truth_value(condition)

View File

@@ -0,0 +1,3 @@
Autotest.add_discovery do
"rubykoan" if File.exist? 'path_to_enlightenment.rb'
end

View File

@@ -0,0 +1,24 @@
require 'autotest'
class Autotest::Rubykoan < Autotest
def initialize
super
@exceptions = /\.txt|Rakefile|\.rdoc/
self.order = :alpha
self.add_mapping(/^about_.*rb$/) do |filename, _|
filename
end
end
def make_test_cmd files_to_test
"#{ruby} 'path_to_enlightenment.rb'"
end
# quiet test/unit chatter
def handle_results(results)
end
end

View File

@@ -1 +1 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')

View File

@@ -6,8 +6,24 @@ require 'test/unit/assertions'
class FillMeInError < StandardError class FillMeInError < StandardError
end end
def __(value="FILL ME IN") def in_ruby_version(version)
value yield if RUBY_VERSION =~ /^#{version}/
end
def __(value="FILL ME IN", value19=:mu)
if RUBY_VERSION < "1.9"
value
else
(value19 == :mu) ? value : value19
end
end
def _n_(value=999999, value19=:mu)
if RUBY_VERSION < "1.9"
value
else
(value19 == :mu) ? value : value19
end
end end
def ___(value=FillMeInError) def ___(value=FillMeInError)
@@ -20,28 +36,98 @@ class Object
self.send(method) self.send(method)
end end
end end
in_ruby_version("1.9") do
public :method_missing
end
end end
module EdgeCase module EdgeCase
class Sensei
attr_reader :failure, :failed_test
AssertionError = Test::Unit::AssertionFailedError module Color
#shamelessly stolen (and modified) from redgreen
COLORS = {
:clear => 0, :black => 30, :red => 31,
:green => 32, :yellow => 33, :blue => 34,
:magenta => 35, :cyan => 36,
}
module_function
COLORS.each do |color, value|
module_eval "def #{color}(string); colorize(string, #{value}); end"
module_function color
end
def colorize(string, color_value)
if ENV['NO_COLOR']
string
else
color(color_value) + string + color(COLORS[:clear])
end
end
def color(color_value)
"\e[#{color_value}m"
end
end
class Sensei
attr_reader :failure, :failed_test, :pass_count
in_ruby_version("1.8") do
AssertionError = Test::Unit::AssertionFailedError
end
in_ruby_version("1.9") do
if defined?(MiniTest)
AssertionError = MiniTest::Assertion
else
AssertionError = Test::Unit::AssertionFailedError
end
end
def initialize def initialize
@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 " #{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 " #{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
@@ -54,21 +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 "You have not yet reached enlightenment ..." encourage
puts failure.message guide_through_error
puts a_zenlike_statement
puts "Please meditate on the following code:" show_progress
if assert_failed? else
puts find_interesting_lines(failure.backtrace) end_screen
else
puts failure.backtrace
end
puts
end end
say_something_zenlike 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)
@@ -79,37 +259,40 @@ 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 say_something_zenlike def a_zenlike_statement
puts
if !failed? if !failed?
puts "Mountains are again merely mountains" zen_statement = "Mountains are again merely mountains"
else else
case (@pass_count % 10) zen_statement = case (@pass_count % 10)
when 0 when 0
puts "mountains are merely mountains" "mountains are merely mountains"
when 1, 2 when 1, 2
puts "learn the rules so you know how to break them properly" "learn the rules so you know how to break them properly"
when 3, 4 when 3, 4
puts "remember that silence is sometimes the best answer" "remember that silence is sometimes the best answer"
when 5, 6 when 5, 6
puts "sleep is the best meditation" "sleep is the best meditation"
when 7, 8 when 7, 8
puts "when you lose, don't lose the lesson" "when you lose, don't lose the lesson"
else else
puts "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
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?
@@ -126,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)
@@ -133,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 "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 => ex
test.failed(ex)
ensure
begin
test.teardown
rescue StandardError => ex
test.failed(ex) if test.passed?
end
end
accumulator.accumulate(test)
end end
def end_of_enlightenment def end_of_enlightenment
@@ -200,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

@@ -1,12 +1,17 @@
# The path to Ruby Enlightenment starts with the following: # The path to Ruby Enlightenment starts with the following:
$LOAD_PATH << File.dirname(__FILE__)
require 'about_asserts' require 'about_asserts'
require 'about_nil' require 'about_nil'
require 'about_arrays' 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_methods' require 'about_methods'
require 'about_constants'
require 'about_control_statements' require 'about_control_statements'
require 'about_true_and_false' require 'about_true_and_false'
require 'about_triangle_project' require 'about_triangle_project'

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutArrayAssignment < EdgeCase::Koan class AboutArrayAssignment < EdgeCase::Koan
def test_non_parallel_assignment def test_non_parallel_assignment
@@ -35,4 +35,11 @@ class AboutArrayAssignment < EdgeCase::Koan
assert_equal __("John"), first_name assert_equal __("John"), first_name
end end
def test_swapping_with_parallel_assignment
first_name = "Roy"
last_name = "Rob"
first_name, last_name = last_name, first_name
assert_equal __('Rob'), first_name
assert_equal __('Roy'), last_name
end
end end

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutArrays < EdgeCase::Koan class AboutArrays < EdgeCase::Koan
def test_creating_arrays def test_creating_arrays

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
# -*- ruby -*- # -*- ruby -*-
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutAsserts < EdgeCase::Koan class AboutAsserts < EdgeCase::Koan

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutBlocks < EdgeCase::Koan class AboutBlocks < EdgeCase::Koan
def method_with_block def method_with_block
@@ -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

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutClassMethods < EdgeCase::Koan class AboutClassMethods < EdgeCase::Koan
class Dog class Dog
@@ -19,11 +19,11 @@ class AboutClassMethods < EdgeCase::Koan
def test_objects_have_methods def test_objects_have_methods
fido = Dog.new fido = Dog.new
assert_equal __(44), fido.methods.size assert fido.methods.size > _n_(30)
end end
def test_classes_have_methods def test_classes_have_methods
assert_equal __(79), Dog.methods.size assert Dog.methods.size > _n_(40)
end end
def test_you_can_define_methods_on_individual_objects def test_you_can_define_methods_on_individual_objects

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutClasses < EdgeCase::Koan class AboutClasses < EdgeCase::Koan
class Dog class Dog
@@ -22,7 +22,7 @@ class AboutClasses < EdgeCase::Koan
assert_equal __([]), fido.instance_variables assert_equal __([]), fido.instance_variables
fido.set_name("Fido") fido.set_name("Fido")
assert_equal __(["@name"]), fido.instance_variables assert_equal __(["@name"], [:@name]), fido.instance_variables
end end
def test_instance_variables_cannot_be_accessed_outside_the_class def test_instance_variables_cannot_be_accessed_outside_the_class
@@ -180,7 +180,7 @@ class AboutClasses < EdgeCase::Koan
def test_all_objects_support_to_s_and_inspect def test_all_objects_support_to_s_and_inspect
array = [1,2,3] array = [1,2,3]
assert_equal __("123"), array.to_s assert_equal __("123", "[1, 2, 3]"), array.to_s
assert_equal __("[1, 2, 3]"), array.inspect assert_equal __("[1, 2, 3]"), array.inspect
assert_equal __("STRING"), "STRING".to_s assert_equal __("STRING"), "STRING".to_s

87
src/about_constants.rb Normal file
View File

@@ -0,0 +1,87 @@
require File.expand_path(File.dirname(__FILE__) + '/edgecase')
C = "top level"
class AboutConstants < EdgeCase::Koan
C = "nested"
def test_nested_constants_may_also_be_referenced_with_relative_paths
assert_equal __("nested"), C
end
def test_top_level_constants_are_referenced_by_double_colons
assert_equal __("top level"), ::C
end
def test_nested_constants_are_referenced_by_their_complete_path
assert_equal __("nested"), AboutConstants::C
assert_equal __("nested"), ::AboutConstants::C
end
# ------------------------------------------------------------------
class Animal
LEGS = 4
def legs_in_animal
LEGS
end
class NestedAnimal
def legs_in_nested_animal
LEGS
end
end
end
def test_nested_classes_inherit_constants_from_enclosing_classes
assert_equal __(4), Animal::NestedAnimal.new.legs_in_nested_animal
end
# ------------------------------------------------------------------
class Reptile < Animal
def legs_in_reptile
LEGS
end
end
def test_subclasses_inherit_constants_from_parent_classes
assert_equal __(4), Reptile.new.legs_in_reptile
end
# ------------------------------------------------------------------
class MyAnimals
LEGS = 2
class Bird < Animal
def legs_in_bird
LEGS
end
end
end
def test_who_wins_with_both_nested_and_inherited_constants
assert_equal __(2), MyAnimals::Bird.new.legs_in_bird
end
# QUESTION: Which has precedence: The constant in the lexical scope,
# or the constant from the inheritance heirarachy?
# ------------------------------------------------------------------
class MyAnimals::Oyster < Animal
def legs_in_oyster
LEGS
end
end
def test_who_wins_with_explicit_scoping_on_class_definition
assert_equal __(4), MyAnimals::Oyster.new.legs_in_oyster
end
# QUESTION: Now Which has precedence: The constant in the lexical
# scope, or the constant from the inheritance heirarachy? Why is it
# different than the previous answer?
end

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutControlStatements < EdgeCase::Koan class AboutControlStatements < EdgeCase::Koan
@@ -8,10 +8,10 @@ class AboutControlStatements < EdgeCase::Koan
else else
result = :false_value result = :false_value
end end
assert_equal __, result assert_equal __(:true_value), result
end end
def test_if_then_else_statements def test_if_then_statements
result = :default_value result = :default_value
if true if true
result = :true_value result = :true_value

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class DiceSet class DiceSet
attr_reader :values attr_reader :values
@@ -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

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutExceptions < EdgeCase::Koan class AboutExceptions < EdgeCase::Koan

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutHashes < EdgeCase::Koan class AboutHashes < EdgeCase::Koan
def test_creating_hashes def test_creating_hashes
@@ -48,7 +48,7 @@ class AboutHashes < EdgeCase::Koan
def test_hash_values def test_hash_values
hash = { :one => "uno", :two => "dos" } hash = { :one => "uno", :two => "dos" }
assert_equal __(2), hash.keys.size assert_equal __(2), hash.values.size
assert_equal __(true), hash.values.include?("uno") assert_equal __(true), hash.values.include?("uno")
assert_equal __(true), hash.values.include?("dos") assert_equal __(true), hash.values.include?("dos")
assert_equal Array, hash.values.class assert_equal Array, hash.values.class

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutInheritance < EdgeCase::Koan class AboutInheritance < EdgeCase::Koan
class Dog class Dog

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutIteration < EdgeCase::Koan class AboutIteration < EdgeCase::Koan
@@ -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

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutMessagePassing < EdgeCase::Koan class AboutMessagePassing < EdgeCase::Koan
@@ -49,7 +49,6 @@ class AboutMessagePassing < EdgeCase::Koan
class MessageCatcher class MessageCatcher
def add_a_payload(*args) def add_a_payload(*args)
return :empty unless args
args args
end end
end end
@@ -90,6 +89,18 @@ class AboutMessagePassing < EdgeCase::Koan
# #
# If the method :method_missing causes the NoMethodError, then # If the method :method_missing causes the NoMethodError, then
# what would happen if we redefine method_missing? # what would happen if we redefine method_missing?
#
# NOTE:
#
# In Ruby 1.8 the method_missing method is public and can be
# called as shown above. However, in Ruby 1.9 the method_missing
# method is private. We explicitly made it public in the testing
# framework so this example works in both versions of Ruby. Just
# keep in mind you can't call method_missing like that in Ruby
# 1.9. normally.
#
# Thanks. We now return you to your regularly schedule Ruby
# Koans.
end end
# ------------------------------------------------------------------ # ------------------------------------------------------------------

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
def my_global_method(a,b) def my_global_method(a,b)
a + b a + b
@@ -10,14 +10,14 @@ class AboutMethods < EdgeCase::Koan
assert_equal __(5), my_global_method(2,3) assert_equal __(5), my_global_method(2,3)
end end
def test_calling_global_methods_without_parenthesis def test_calling_global_methods_without_parentheses
result = my_global_method 2, 3 result = my_global_method 2, 3
assert_equal __(5), result assert_equal __(5), result
end end
# (NOTE: We are Using eval below because the example code is # (NOTE: We are Using eval below because the example code is
# considered to be syntactically invalid). # considered to be syntactically invalid).
def test_sometimes_missing_parenthesis_are_ambiguous def test_sometimes_missing_parentheses_are_ambiguous
#-- #--
eval "assert_equal 5, my_global_method(2, 3)" # REMOVE CHECK eval "assert_equal 5, my_global_method(2, 3)" # REMOVE CHECK
if false if false
@@ -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

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutModules < EdgeCase::Koan class AboutModules < EdgeCase::Koan
module Nameable module Nameable

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutNil < EdgeCase::Koan class AboutNil < EdgeCase::Koan
def test_nil_is_an_object def test_nil_is_an_object

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutOpenClasses < EdgeCase::Koan class AboutOpenClasses < EdgeCase::Koan
class Dog class Dog

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
# Project: Create a Proxy Class # Project: Create a Proxy Class
# #

View File

@@ -0,0 +1,159 @@
# -*- coding: utf-8 -*-
require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutRegularExpressions < EdgeCase::Koan
def test_a_pattern_is_a_regular_expression
assert_equal Regexp, /pattern/.class
end
def test_a_regexp_can_search_a_string_for_matching_content
assert_equal "match", "some matching content"[/match/]
end
def test_a_failed_match_returns_nil
assert_equal __(nil), "some matching content"[/missing/]
end
# ------------------------------------------------------------------
def test_question_mark_means_optional
assert_equal __("ab"), "abbcccddddeeeee"[/ab?/]
assert_equal __("a"), "abbcccddddeeeee"[/az?/]
end
def test_plus_means_one_or_more
assert_equal __("bccc"), "abbcccddddeeeee"[/bc+/]
end
def test_asterisk_means_zero_or_more
assert_equal __("abb"), "abbcccddddeeeee"[/ab*/]
assert_equal __("a"), "abbcccddddeeeee"[/az*/]
assert_equal __(""), "abbcccddddeeeee"[/z*/]
# THINK ABOUT IT:
#
# When would * fail to match?
end
# THINK ABOUT IT:
#
# We say that the repetition operators above are "greedy."
#
# Why?
# ------------------------------------------------------------------
def test_the_left_most_match_wins
assert_equal __("a"), "abbccc az"[/az*/]
end
# ------------------------------------------------------------------
def test_character_classes_give_options_for_a_character
animals = ["cat", "bat", "rat", "zat"]
assert_equal __(["cat", "bat", "rat"]), animals.select { |a| a[/[cbr]at/] }
end
def test_slash_d_is_a_shortcut_for_a_digit_character_class
assert_equal __("42"), "the number is 42"[/[0123456789]+/]
assert_equal __("42"), "the number is 42"[/\d+/]
end
def test_character_classes_can_include_ranges
assert_equal __("42"), "the number is 42"[/[0-9]+/]
end
def test_slash_s_is_a_shortcut_for_a_whitespace_character_class
assert_equal __(" \t\n"), "space: \t\n"[/\s+/]
end
def test_slash_w_is_a_shortcut_for_a_word_character_class
# NOTE: This is more like how a programmer might define a word.
assert_equal __("variable_1"), "variable_1 = 42"[/[a-zA-Z0-9_]+/]
assert_equal __("variable_1"), "variable_1 = 42"[/\w+/]
end
def test_period_is_a_shortcut_for_any_non_newline_character
assert_equal __("abc"), "abc\n123"[/a.+/]
end
def test_a_character_class_can_be_negated
assert_equal __("the number is "), "the number is 42"[/[^0-9]+/]
end
def test_shortcut_character_classes_are_negated_with_capitals
assert_equal __("the number is "), "the number is 42"[/\D+/]
assert_equal __("space:"), "space: \t\n"[/\S+/]
assert_equal __(" = "), "variable_1 = 42"[/\W+/]
end
# ------------------------------------------------------------------
def test_slash_a_anchors_to_the_start_of_the_string
assert_equal __("start"), "start end"[/\Astart/]
assert_equal __(nil), "start end"[/\Aend/]
end
def test_slash_z_anchors_to_the_end_of_the_string
assert_equal __("end"), "start end"[/end\z/]
assert_equal __(nil), "start end"[/start\z/]
end
def test_caret_anchors_to_the_start_of_lines
assert_equal __("2"), "num 42\n2 lines"[/^\d+/]
end
def test_dollar_sign_anchors_to_the_end_of_lines
assert_equal __("42"), "2 lines\nnum 42"[/\d+$/]
end
def test_slash_b_anchors_to_a_word_boundary
assert_equal __("vines"), "bovine vines"[/\bvine./]
end
# ------------------------------------------------------------------
def test_parentheses_group_contents
assert_equal __("hahaha"), "ahahaha"[/(ha)+/]
end
# ------------------------------------------------------------------
def test_parentheses_also_capture_matched_content_by_number
assert_equal __("Gray"), "Gray, James"[/(\w+), (\w+)/, 1]
assert_equal __("James"), "Gray, James"[/(\w+), (\w+)/, 2]
end
def test_variables_can_also_be_used_to_access_captures
assert_equal __("Gray, James"), "Name: Gray, James"[/(\w+), (\w+)/]
assert_equal __("Gray"), $1
assert_equal __("James"), $2
end
# ------------------------------------------------------------------
def test_a_vertical_pipe_means_or
grays = /(James|Dana|Summer) Gray/
assert_equal __("James Gray"), "James Gray"[grays]
assert_equal __("Summer"), "Summer Gray"[grays, 1]
assert_equal __(nil), "Jim Gray"[grays, 1]
end
# THINK ABOUT IT:
#
# Explain the difference between a character class ([...]) and alternation (|).
# ------------------------------------------------------------------
def test_scan_is_like_find_all
assert_equal __(["one", "two", "three"]), "one two-three".scan(/\w+/)
end
def test_sub_is_like_find_and_replace
assert_equal __("one t-three"), "one two-three".sub(/(t\w*)/) { $1[0, 1] }
end
def test_gsub_is_like_find_and_replace_all
assert_equal __("one t-t"), "one two-three".gsub(/(t\w*)/) { $1[0, 1] }
end
end

View File

@@ -1,6 +1,6 @@
require '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

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutScope < EdgeCase::Koan class AboutScope < EdgeCase::Koan
module Jims module Jims
@@ -73,7 +73,7 @@ class AboutScope < EdgeCase::Koan
end end
def test_you_can_get_a_list_of_constants_for_any_class_or_module def test_you_can_get_a_list_of_constants_for_any_class_or_module
assert_equal __(["Dog"]), Jims.constants assert_equal __(["Dog"], [:Dog]), Jims.constants
assert_equal __(122), Object.constants.size assert Object.constants.size > _n_(10)
end end
end end

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
# Greed is a dice game where you roll up to five dice to accumulate # Greed is a dice game where you roll up to five dice to accumulate
# points. The following "score" function will be used calculate the # points. The following "score" function will be used calculate the
@@ -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

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutStrings < EdgeCase::Koan class AboutStrings < EdgeCase::Koan
def test_double_quoted_strings_are_strings def test_double_quoted_strings_are_strings
@@ -141,16 +141,25 @@ EOS
def test_you_can_get_a_single_character_from_a_string def test_you_can_get_a_single_character_from_a_string
string = "Bacon, lettuce and tomato" string = "Bacon, lettuce and tomato"
assert_equal __(97), string[1] assert_equal __(97, 'a'), string[1]
# Surprised? # Surprised?
end end
def test_single_characters_are_represented_by_integers in_ruby_version("1.8") do
assert_equal __(97), ?a def test_in_ruby_1_8_single_characters_are_represented_by_integers
assert_equal __(true), ?a == 97 assert_equal __(97, 'a'), ?a
assert_equal __(true, false), ?a == 97
assert_equal __(true), ?b == (?a + 1) assert_equal __(true), ?b == (?a + 1)
end
end
in_ruby_version("1.9") do
def test_in_ruby_1_9_single_characters_are_represented_by_strings
assert_equal __('a'), ?a
assert_equal __(false), ?a == 97
end
end end
def test_strings_can_be_split def test_strings_can_be_split
@@ -165,8 +174,8 @@ EOS
assert_equal [__("the"), __("rain"), __("in"), __("spain")], words assert_equal [__("the"), __("rain"), __("in"), __("spain")], words
# NOTE: Patterns are formed from Regular Expressions. Ruby has a # NOTE: Patterns are formed from Regular Expressions. Ruby has a
# very powerful Regular Expression library. Unfortunately, time # very powerful Regular Expression library. We will become
# does not permit us to explore it in detail in Ruby 101. # enlightened about them soon.
end end
def test_strings_can_be_joined def test_strings_can_be_joined

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

@@ -1,9 +1,9 @@
require 'edgecase' 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

@@ -1,14 +1,15 @@
require 'edgecase' 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
assert_raise(TriangleError) do triangle(0, 0, 0) end assert_raise(TriangleError) do triangle(0, 0, 0) end
assert_raise(TriangleError) do triangle(3, 4, -5) end assert_raise(TriangleError) do triangle(3, 4, -5) end
assert_raise(TriangleError) do triangle(1, 1, 3) end
assert_raise(TriangleError) do triangle(2, 4, 2) end assert_raise(TriangleError) do triangle(2, 4, 2) end
end end
end end

View File

@@ -1,4 +1,4 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutTrueAndFalse < EdgeCase::Koan class AboutTrueAndFalse < EdgeCase::Koan
def truth_value(condition) def truth_value(condition)

3
src/autotest/discover.rb Normal file
View File

@@ -0,0 +1,3 @@
Autotest.add_discovery do
"rubykoan" if File.exist? 'path_to_enlightenment.rb'
end

24
src/autotest/rubykoan.rb Normal file
View File

@@ -0,0 +1,24 @@
require 'autotest'
class Autotest::Rubykoan < Autotest
def initialize
super
@exceptions = /\.txt|Rakefile|\.rdoc/
self.order = :alpha
self.add_mapping(/^about_.*rb$/) do |filename, _|
filename
end
end
def make_test_cmd files_to_test
"#{ruby} 'path_to_enlightenment.rb'"
end
# quiet test/unit chatter
def handle_results(results)
end
end

View File

@@ -1 +1 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')

View File

@@ -6,8 +6,30 @@ require 'test/unit/assertions'
class FillMeInError < StandardError class FillMeInError < StandardError
end end
def __(value="FILL ME IN") def ruby_version?(version)
value 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
def __(value="FILL ME IN", value19=:mu)
if RUBY_VERSION < "1.9"
value
else
(value19 == :mu) ? value : value19
end
end
def _n_(value=999999, value19=:mu)
if RUBY_VERSION < "1.9"
value
else
(value19 == :mu) ? value : value19
end
end end
def ___(value=FillMeInError) def ___(value=FillMeInError)
@@ -20,28 +42,98 @@ class Object
self.send(method) self.send(method)
end end
end end
in_ruby_version("1.9") do
public :method_missing
end
end end
module EdgeCase module EdgeCase
class Sensei
attr_reader :failure, :failed_test
AssertionError = Test::Unit::AssertionFailedError module Color
#shamelessly stolen (and modified) from redgreen
COLORS = {
:clear => 0, :black => 30, :red => 31,
:green => 32, :yellow => 33, :blue => 34,
:magenta => 35, :cyan => 36,
}
module_function
COLORS.each do |color, value|
module_eval "def #{color}(string); colorize(string, #{value}); end"
module_function color
end
def colorize(string, color_value)
if ENV['NO_COLOR']
string
else
color(color_value) + string + color(COLORS[:clear])
end
end
def color(color_value)
"\e[#{color_value}m"
end
end
class Sensei
attr_reader :failure, :failed_test, :pass_count
in_ruby_version("1.8") do
AssertionError = Test::Unit::AssertionFailedError
end
in_ruby_version("1.9") do
if defined?(MiniTest)
AssertionError = MiniTest::Assertion
else
AssertionError = Test::Unit::AssertionFailedError
end
end
def initialize def initialize
@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 " #{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 " #{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
@@ -54,21 +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 "You have not yet reached enlightenment ..." encourage
puts failure.message guide_through_error
puts a_zenlike_statement
puts "Please meditate on the following code:" show_progress
if assert_failed? else
puts find_interesting_lines(failure.backtrace) end_screen
else
puts failure.backtrace
end
puts
end end
say_something_zenlike 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)
@@ -79,37 +266,40 @@ 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 say_something_zenlike def a_zenlike_statement
puts
if !failed? if !failed?
puts "Mountains are again merely mountains" zen_statement = "Mountains are again merely mountains"
else else
case (@pass_count % 10) zen_statement = case (@pass_count % 10)
when 0 when 0
puts "mountains are merely mountains" "mountains are merely mountains"
when 1, 2 when 1, 2
puts "learn the rules so you know how to break them properly" "learn the rules so you know how to break them properly"
when 3, 4 when 3, 4
puts "remember that silence is sometimes the best answer" "remember that silence is sometimes the best answer"
when 5, 6 when 5, 6
puts "sleep is the best meditation" "sleep is the best meditation"
when 7, 8 when 7, 8
puts "when you lose, don't lose the lesson" "when you lose, don't lose the lesson"
else else
puts "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
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?
@@ -126,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)
@@ -133,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 "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 => ex
test.failed(ex)
ensure
begin
test.teardown
rescue StandardError => ex
test.failed(ex) if test.passed?
end
end
accumulator.accumulate(test)
end end
def end_of_enlightenment def end_of_enlightenment
@@ -200,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

@@ -1,12 +1,17 @@
# The path to Ruby Enlightenment starts with the following: # The path to Ruby Enlightenment starts with the following:
$LOAD_PATH << File.dirname(__FILE__)
require 'about_asserts' require 'about_asserts'
require 'about_nil' require 'about_nil'
require 'about_arrays' 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_methods' require 'about_methods'
require 'about_constants'
require 'about_control_statements' require 'about_control_statements'
require 'about_true_and_false' require 'about_true_and_false'
require 'about_triangle_project' require 'about_triangle_project'