105 Commits

Author SHA1 Message Date
Jim Weirich
db3bbdcf45 Better test name in the java interop koan 2010-12-23 15:04:16 -05:00
Jim Weirich
1a0d82a402 Added .rbc to clean list. 2010-12-23 12:28:39 -05:00
Jim Weirich
c8b4c3e6c5 [5458710] Added koans about to_str 2010-12-23 12:28:24 -05:00
Jim Weirich
204cd44ea9 [5462710] Fixed equality koan on symbols. 2010-12-23 12:08:03 -05:00
Jim Weirich
325ad5bce3 Result summary now in order of running. 2010-12-23 11:54:41 -05:00
Jim Weirich
b733076165 Added .rbc to ignore list 2010-12-23 11:48:40 -05:00
Jim Weirich
5e9083e754 Remove dbgs from Rakefile 2010-12-23 11:48:15 -05:00
Jim Weirich
ab59dc4791 Merge branch 'more_jruby' into m
* more_jruby:
  [5553333] Updated java interop on to_java method.
  Added cruise task
  java coercion
2010-12-23 11:46:29 -05:00
Jim Weirich
ccfa664b48 [5553333] Updated java interop on to_java method. 2010-12-23 11:45:46 -05:00
Jim Weirich
9b9eb640f8 Added cruise task 2010-12-23 11:29:39 -05:00
Jim Weirich
297cfde6a3 [7439987] Changed symbol inclusion test to use strings. 2010-12-23 10:16:34 -05:00
Jim Weirich
3dab146b8d Added --/++ markers around the dice class. 2010-12-23 09:26:55 -05:00
Marc Peabody
0b9727f299 fix mistake in about_symbols.rb 2010-12-14 09:12:49 -05:00
Jim Weirich
d3ce64a768 Updatd exception koan to be clearer about StandardError VS RuntimeError 2010-11-29 09:02:22 -05:00
Luke Pearce
9291f534c6 Fixed exceptions typo 2010-11-29 13:24:40 +00:00
Jim Weirich
71f670ac1d Disabled colored output on windows. 2010-10-21 07:05:06 -04:00
Jim Weirich
1864c9c346 Updated koans for about_symbols fix 2010-10-21 07:04:54 -04:00
Jim Weirich
cfd5b6bbe1 Fixed test_symbols_with_spaces_can_be_built method name collision. 2010-10-21 07:02:10 -04:00
Marc Peabody
e26666280c java coercion 2010-10-02 10:11:18 -04:00
Jim Weirich
e5843f64fd Fixed bug where some koans were not generated properly. 2010-10-01 11:05:25 -04:00
Jim Weirich
fdb8774c95 Added Java Interop koans if running jruby. 2010-09-28 14:49:43 -04:00
Jim Weirich
5b483df29b Added descriptions to rake tasks. 2010-09-28 14:49:20 -04:00
Jim Weirich
7dee146a8c Added missing __ replacements 2010-09-28 14:49:06 -04:00
Jim Weirich
e24d94eeff Updated koans from source. 2010-09-28 14:43:34 -04:00
Jim Weirich
4789e831df Added enumerable collections and open java classes to jruby koan. 2010-09-28 14:43:03 -04:00
Jim Weirich
b41d6167b4 Updated koans from src. 2010-09-28 14:17:46 -04:00
Jim Weirich
c0bbe773d9 Moved splat example to later + several typo corrections. 2010-09-28 14:15:08 -04:00
Srdjan Pejic
6fb8e3c3af Cool test; Still can't believe there is no standard implementation of first/rest 2010-09-28 14:11:08 -04:00
Marc Peabody + Jim Weirich
1dcd9babd4 Added more think about its to the ruby/java string comparisons. 2010-09-27 18:24:25 -04:00
Marc Peabody + Jim Weirich
45523c03c0 More class conflict examples. 2010-09-27 16:54:10 -04:00
Jim Weirich
8436b46c1a Added Ruby version information to the output. 2010-09-27 14:36:38 -04:00
Jim Weirich
322ca38767 Minitest lines in backtrace are no longer interesting. 2010-09-27 14:15:59 -04:00
Jim Weirich
91f15dc690 Simplified output for running against multiple rubies. 2010-09-27 14:09:38 -04:00
Jim Weirich
beb7fe591e Added rake task for running koans against several rubies. 2010-09-27 13:46:21 -04:00
Jim Weirich
17fb071814 Evaluated each assert line without a replacement test specified. 2010-09-27 13:38:50 -04:00
Jim Weirich
012cb20bb3 Moved checks into a separate rake file. 2010-09-27 13:38:22 -04:00
Jim Weirich
ece35b2539 Added symbol & string identity tests. 2010-09-27 11:36:38 -04:00
Jim Weirich
fe2ee86172 Added Koan on basic objects 2010-09-27 11:36:00 -04:00
Jim Weirich
9caf7a950a Updated koans from source directory. 2010-09-27 10:50:29 -04:00
Jim Weirich
a13f184ef1 Merge branch 'output_refactoring'
* output_refactoring:
  Updated for JRuby
  Switch blue color to cyan for better contrast on dark terminals.
  80 char limit to end screen
  Updated Koans directory
  Added else clause if progress file is not there.
  regen for updated koans/edgecase.rb
  end screen with koans logo ascii art
2010-09-27 10:49:17 -04:00
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
1bc1b8e50c Add timestamps to package output 2010-09-21 17:48:30 -04:00
Matt Yoho
9b5dfb4e42 Add koan on asserting exceptions 2010-09-21 17:12:50 -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
78 changed files with 2454 additions and 452 deletions

3
.gitignore vendored
View File

@@ -1 +1,4 @@
dist dist
.project_env.rc
.path_progress
*.rbc

View File

@@ -1,29 +1,29 @@
= EdgeCase Ruby Koans = EdgeCase Ruby Koans
The Ruby Koans walk you along the path to enlightenment in order to learn Ruby. The Ruby Koans walk you along the path to enlightenment in order to learn Ruby.
The goal is to learn the Ruby language, syntax, structure, and some common The goal is to learn the Ruby language, syntax, structure, and some common
functions and libraries. We also teach you culture. Testing is not just something we functions and libraries. We also teach you culture. Testing is not just something we
pay lip service to, but something we live. It is essential in your quest to learn pay lip service to, but something we live. It is essential in your quest to learn
and do great things in the language. and do great things in the language.
== The Structure == The Structure
The koans are broken out into areas by file, hashes are covered in about_hashes.rb, The koans are broken out into areas by file, hashes are covered in about_hashes.rb,
modules are introduced in about_modules.rb, etc. They are presented in order in the modules are introduced in about_modules.rb, etc. They are presented in order in the
path_to_enlightenment.rb file. path_to_enlightenment.rb file.
Each koan builds up your knowledge of Ruby and builds upon itself. It will stop at Each koan builds up your knowledge of Ruby and builds upon itself. It will stop at
the first place you need to correct. the first place you need to correct.
Some koans simply need to have the correct answer substituted for an incorrect one. Some koans simply need to have the correct answer substituted for an incorrect one.
Some, however, require you to supply your own answer. If you see the method +__+ (a Some, however, require you to supply your own answer. If you see the method +__+ (a
double underscore) listed, it is a hint to you to supply your own code in order to double underscore) listed, it is a hint to you to supply your own code in order to
make it work correctly. make it work correctly.
== Installing Ruby == Installing Ruby
If you do not have Ruby setup, please visit http://ruby-lang.org/en/downloads/ for If you do not have Ruby setup, please visit http://ruby-lang.org/en/downloads/ for
operating specific instructions. In order to run this you need ruby and rake operating specific instructions. In order to run this you need ruby and rake
installed. To check the installations simply type: installed. To check the installations simply type:
*nix platforms from any terminal window: *nix platforms from any terminal window:
@@ -33,11 +33,11 @@ installed. To check the installations simply type:
Windows from the command prompt (cmd.exe) Windows from the command prompt (cmd.exe)
c:\ruby --version c:\ruby --version
c:\rake --version c:\rake --version
Any response for Ruby with a version number greater than 1.8 is fine (should be Any response for Ruby with a version number greater than 1.8 is fine (should be
around 1.8.6 or more). Any version of rake will do. around 1.8.6 or more). Any version of rake will do.
== The Path To Enlightenment == The Path To Enlightenment
@@ -48,18 +48,18 @@ recommended way to run them as we might build more functionality into this task)
[ruby_koans] $ rake # runs the default target :walk_the_path [ruby_koans] $ rake # runs the default target :walk_the_path
[ruby_koans] $ ruby path_to_enlightenment.rb # simply call the file directly [ruby_koans] $ ruby path_to_enlightenment.rb # simply call the file directly
Windows is the same thing Windows is the same thing
c:\ruby_koans\rake # runs the default target :walk_the_path c:\ruby_koans\rake # runs the default target :walk_the_path
c:\ruby_koans\ruby path_to_enlightenment.rb # simply call the file directly c:\ruby_koans\ruby path_to_enlightenment.rb # simply call the file directly
=== Red, Green, Refactor === Red, Green, Refactor
In test-driven development the mantra has always been, red, green, refactor. Write a In test-driven development the mantra has always been, red, green, refactor. Write a
failing test and run it (red), make the test pass (green), then refactor it (that is failing test and run it (red), make the test pass (green), then refactor it (that is
look at the code and see if you can make it any better. In this case you will need look at the code and see if you can make it any better. In this case you will need
to run the koan and see it fail (red), make the test pass (green), then take a to run the koan and see it fail (red), make the test pass (green), then take a
moment and reflect upon the test to see what it is teaching you and improve the moment and reflect upon the test to see what it is teaching you and improve the
code to better communicate its intent (refactor). code to better communicate its intent (refactor).
@@ -68,26 +68,26 @@ The very first time you run it you will see the following output:
[ ruby_koans ] $ rake [ ruby_koans ] $ rake
(in /Users/person/dev/ruby_koans) (in /Users/person/dev/ruby_koans)
cd koans cd koans
Thinking AboutAsserts Thinking AboutAsserts
test_assert_truth has damaged your karma. test_assert_truth has damaged your karma.
You have not yet reached enlightenment ... You have not yet reached enlightenment ...
<false> is not true. <false> is not true.
Please meditate on the following code: Please meditate on the following code:
./about_asserts.rb:10:in `test_assert_truth' ./about_asserts.rb:10:in `test_assert_truth'
path_to_enlightenment.rb:27 path_to_enlightenment.rb:27
mountains are merely mountains mountains are merely mountains
You have come to your first stage. If you notice it is telling you where to look for You have come to your first stage. If you notice it is telling you where to look for
the first solution: the first solution:
Please meditate on the following code: Please meditate on the following code:
./about_asserts.rb:10:in `test_assert_truth' ./about_asserts.rb:10:in `test_assert_truth'
path_to_enlightenment.rb:27 path_to_enlightenment.rb:27
We then open up the about_asserts.rb file and look at the first test: We then open up the about_asserts.rb file and look at the first test:
# We shall contemplate truth by testing reality, via asserts. # We shall contemplate truth by testing reality, via asserts.
@@ -97,13 +97,13 @@ We then open up the about_asserts.rb file and look at the first test:
We then change the +false+ to +true+ and run the test again. After you are We then change the +false+ to +true+ and run the test again. After you are
done, think about what you are learning. In this case, ignore everything except done, think about what you are learning. In this case, ignore everything except
the method name (+test_assert_truth+) and the parts inside the method (everything the method name (+test_assert_truth+) and the parts inside the method (everything
before the +end+). before the +end+).
In this case the goal is for you to see that if you pass a value to the +assert+ In this case the goal is for you to see that if you pass a value to the +assert+
method, it will either ensure it is +true+ and continue on, or fail if in fact method, it will either ensure it is +true+ and continue on, or fail if in fact
the statement is +false+. the statement is +false+.
== Inspiration == Inspiration
A special thanks to Mike Clark and Ara Howard for inspiring this A special thanks to Mike Clark and Ara Howard for inspiring this

View File

@@ -11,23 +11,33 @@ DIST_DIR = 'dist'
SRC_FILES = FileList["#{SRC_DIR}/*"] SRC_FILES = FileList["#{SRC_DIR}/*"]
KOAN_FILES = SRC_FILES.pathmap("#{PROB_DIR}/%f") KOAN_FILES = SRC_FILES.pathmap("#{PROB_DIR}/%f")
TAR_FILE = "#{DIST_DIR}/rubykoans.tgz" today = Time.now.strftime("%Y-%m-%d")
ZIP_FILE = "#{DIST_DIR}/rubykoans.zip" TAR_FILE = "#{DIST_DIR}/rubykoans-#{today}.tgz"
ZIP_FILE = "#{DIST_DIR}/rubykoans-#{today}.zip"
CLEAN.include("**/*.rbc")
CLOBBER.include(DIST_DIR) CLOBBER.include(DIST_DIR)
module Koans module Koans
# Remove solution info from source
# __(a,b) => __
# _n_(number) => __
# # __ =>
def Koans.remove_solution(line) def Koans.remove_solution(line)
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.gsub(/\s*#\s*__\s*$/, '')
line line
end end
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|
@@ -48,6 +58,27 @@ module Koans
end end
end end
module RubyImpls
# Calculate the list of relevant Ruby implementations.
def self.find_ruby_impls
rubys = `rvm list`.gsub(/=>/,'').split(/\n/).sort
expected.map { |impl|
last = rubys.grep(Regexp.new(Regexp.quote(impl))).last
last ? last.split.first : nil
}.compact
end
# Return a (cached) list of relevant Ruby implementations.
def self.list
@list ||= find_ruby_impls
end
# List of expected ruby implementations.
def self.expected
%w(ruby-1.8.6 ruby-1.8.7 ruby-1.9.2 jruby ree)
end
end
task :default => :walk_the_path task :default => :walk_the_path
task :walk_the_path do task :walk_the_path do
@@ -80,14 +111,6 @@ task :upload => [TAR_FILE, ZIP_FILE] do
sh "scp #{ZIP_FILE} linode:sites/onestepback.org/download" sh "scp #{ZIP_FILE} linode:sites/onestepback.org/download"
end end
desc "Check that the require files match the about_* files"
task :check do
about_files = Dir['src/about_*.rb'].size
about_requires = `grep require src/path_to_enlightenment.rb | wc -l`.to_i
puts "# of about files: #{about_files}"
puts "# of about requires: #{about_requires}"
end
desc "Generate the Koans from the source files from scratch." desc "Generate the Koans from the source files from scratch."
task :regen => [:clobber_koans, :gen] task :regen => [:clobber_koans, :gen]
@@ -106,3 +129,38 @@ SRC_FILES.each do |koan_src|
Koans.make_koan_file koan_src, t.name Koans.make_koan_file koan_src, t.name
end end
end end
task :run do
puts 'koans'
Dir.chdir("src") do
puts "in #{Dir.pwd}"
sh "ruby path_to_enlightenment.rb"
end
end
desc "Pre-checkin tests (=> run_all)"
task :cruise => :run_all
desc "Run the completed koans againts a list of relevant Ruby Implementations"
task :run_all do
results = []
RubyImpls.list.each do |impl|
puts "=" * 40
puts "On Ruby #{impl}"
sh "rvm #{impl} rake run"
results << [impl, "RAN"]
puts
end
puts "=" * 40
puts "Summary:"
puts
results.each do |impl, res|
puts "#{impl} => RAN"
end
puts
RubyImpls.expected.each do |requested_impl|
impl_pattern = Regexp.new(Regexp.quote(requested_impl))
puts "No Results for #{requested_impl}" unless results.detect { |x| x.first =~ impl_pattern }
end
end

View File

@@ -1,29 +1,29 @@
= EdgeCase Ruby Koans = EdgeCase Ruby Koans
The Ruby Koans walk you along the path to enlightenment in order to learn Ruby. The Ruby Koans walk you along the path to enlightenment in order to learn Ruby.
The goal is to learn the Ruby language, syntax, structure, and some common The goal is to learn the Ruby language, syntax, structure, and some common
functions and libraries. We also teach you culture. Testing is not just something we functions and libraries. We also teach you culture. Testing is not just something we
pay lip service to, but something we live. It is essential in your quest to learn pay lip service to, but something we live. It is essential in your quest to learn
and do great things in the language. and do great things in the language.
== The Structure == The Structure
The koans are broken out into areas by file, hashes are covered in about_hashes.rb, The koans are broken out into areas by file, hashes are covered in about_hashes.rb,
modules are introduced in about_modules.rb, etc. They are presented in order in the modules are introduced in about_modules.rb, etc. They are presented in order in the
path_to_enlightenment.rb file. path_to_enlightenment.rb file.
Each koan builds up your knowledge of Ruby and builds upon itself. It will stop at Each koan builds up your knowledge of Ruby and builds upon itself. It will stop at
the first place you need to correct. the first place you need to correct.
Some koans simply need to have the correct answer substituted for an incorrect one. Some koans simply need to have the correct answer substituted for an incorrect one.
Some, however, require you to supply your own answer. If you see the method +__+ (a Some, however, require you to supply your own answer. If you see the method +__+ (a
double underscore) listed, it is a hint to you to supply your own code in order to double underscore) listed, it is a hint to you to supply your own code in order to
make it work correctly. make it work correctly.
== Installing Ruby == Installing Ruby
If you do not have Ruby setup, please visit http://ruby-lang.org/en/downloads/ for If you do not have Ruby setup, please visit http://ruby-lang.org/en/downloads/ for
operating specific instructions. In order to run this you need ruby and rake operating specific instructions. In order to run this you need ruby and rake
installed. To check the installations simply type: installed. To check the installations simply type:
*nix platforms from any terminal window: *nix platforms from any terminal window:
@@ -33,11 +33,11 @@ installed. To check the installations simply type:
Windows from the command prompt (cmd.exe) Windows from the command prompt (cmd.exe)
c:\ruby --version c:\ruby --version
c:\rake --version c:\rake --version
Any response for Ruby with a version number greater than 1.8 is fine (should be Any response for Ruby with a version number greater than 1.8 is fine (should be
around 1.8.6 or more). Any version of rake will do. around 1.8.6 or more). Any version of rake will do.
== The Path To Enlightenment == The Path To Enlightenment
@@ -48,18 +48,18 @@ recommended way to run them as we might build more functionality into this task)
[ruby_koans] $ rake # runs the default target :walk_the_path [ruby_koans] $ rake # runs the default target :walk_the_path
[ruby_koans] $ ruby path_to_enlightenment.rb # simply call the file directly [ruby_koans] $ ruby path_to_enlightenment.rb # simply call the file directly
Windows is the same thing Windows is the same thing
c:\ruby_koans\rake # runs the default target :walk_the_path c:\ruby_koans\rake # runs the default target :walk_the_path
c:\ruby_koans\ruby path_to_enlightenment.rb # simply call the file directly c:\ruby_koans\ruby path_to_enlightenment.rb # simply call the file directly
=== Red, Green, Refactor === Red, Green, Refactor
In test-driven development the mantra has always been, red, green, refactor. Write a In test-driven development the mantra has always been, red, green, refactor. Write a
failing test and run it (red), make the test pass (green), then refactor it (that is failing test and run it (red), make the test pass (green), then refactor it (that is
look at the code and see if you can make it any better. In this case you will need look at the code and see if you can make it any better. In this case you will need
to run the koan and see it fail (red), make the test pass (green), then take a to run the koan and see it fail (red), make the test pass (green), then take a
moment and reflect upon the test to see what it is teaching you and improve the moment and reflect upon the test to see what it is teaching you and improve the
code to better communicate its intent (refactor). code to better communicate its intent (refactor).
@@ -68,26 +68,26 @@ The very first time you run it you will see the following output:
[ ruby_koans ] $ rake [ ruby_koans ] $ rake
(in /Users/person/dev/ruby_koans) (in /Users/person/dev/ruby_koans)
cd koans cd koans
Thinking AboutAsserts Thinking AboutAsserts
test_assert_truth has damaged your karma. test_assert_truth has damaged your karma.
You have not yet reached enlightenment ... You have not yet reached enlightenment ...
<false> is not true. <false> is not true.
Please meditate on the following code: Please meditate on the following code:
./about_asserts.rb:10:in `test_assert_truth' ./about_asserts.rb:10:in `test_assert_truth'
path_to_enlightenment.rb:27 path_to_enlightenment.rb:27
mountains are merely mountains mountains are merely mountains
You have come to your first stage. If you notice it is telling you where to look for You have come to your first stage. If you notice it is telling you where to look for
the first solution: the first solution:
Please meditate on the following code: Please meditate on the following code:
./about_asserts.rb:10:in `test_assert_truth' ./about_asserts.rb:10:in `test_assert_truth'
path_to_enlightenment.rb:27 path_to_enlightenment.rb:27
We then open up the about_asserts.rb file and look at the first test: We then open up the about_asserts.rb file and look at the first test:
# We shall contemplate truth by testing reality, via asserts. # We shall contemplate truth by testing reality, via asserts.
@@ -97,13 +97,13 @@ We then open up the about_asserts.rb file and look at the first test:
We then change the +false+ to +true+ and run the test again. After you are We then change the +false+ to +true+ and run the test again. After you are
done, think about what you are learning. In this case, ignore everything except done, think about what you are learning. In this case, ignore everything except
the method name (+test_assert_truth+) and the parts inside the method (everything the method name (+test_assert_truth+) and the parts inside the method (everything
before the +end+). before the +end+).
In this case the goal is for you to see that if you pass a value to the +assert+ In this case the goal is for you to see that if you pass a value to the +assert+
method, it will either ensure it is +true+ and continue on, or fail if in fact method, it will either ensure it is +true+ and continue on, or fail if in fact
the statement is +false+. the statement is +false+.
== Inspiration == Inspiration
A special thanks to Mike Clark and Ara Howard for inspiring this A special thanks to Mike Clark and Ara Howard for inspiring this

View File

@@ -1,11 +1,11 @@
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
names = ["John", "Smith"] names = ["John", "Smith"]
assert_equal __, names assert_equal __, names
end end
def test_parallel_assignments def test_parallel_assignments
first_name, last_name = ["John", "Smith"] first_name, last_name = ["John", "Smith"]
assert_equal __, first_name assert_equal __, first_name
@@ -18,13 +18,19 @@ class AboutArrayAssignment < EdgeCase::Koan
assert_equal __, last_name assert_equal __, last_name
end end
def test_parallel_assignments_with_extra_variables def test_parallel_assignments_with_splat_operator
first_name, *last_name = ["John", "Smith", "III"]
assert_equal __, first_name
assert_equal __, last_name
end
def test_parallel_assignments_with_too_few_variables
first_name, last_name = ["Cher"] first_name, last_name = ["Cher"]
assert_equal __, first_name assert_equal __, first_name
assert_equal __, last_name assert_equal __, last_name
end end
def test_parallel_assignements_with_subarrays def test_parallel_assignments_with_subarrays
first_name, last_name = [["Willie", "Rae"], "Johnson"] first_name, last_name = [["Willie", "Rae"], "Johnson"]
assert_equal __, first_name assert_equal __, first_name
assert_equal __, last_name assert_equal __, last_name
@@ -35,4 +41,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,9 +1,9 @@
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
empty_array = Array.new empty_array = Array.new
assert_equal Array, empty_array.class assert_equal __, empty_array.class
assert_equal __, empty_array.size assert_equal __, empty_array.size
end end
@@ -45,9 +45,9 @@ class AboutArrays < EdgeCase::Koan
end end
def test_arrays_and_ranges def test_arrays_and_ranges
assert_equal Range, (1..5).class assert_equal __, (1..5).class
assert_not_equal [1,2,3,4,5], (1..5) assert_not_equal [1,2,3,4,5], (1..5)
assert_equal [1,2,3,4,5], (1..5).to_a assert_equal __, (1..5).to_a
assert_equal __, (1...5).to_a assert_equal __, (1...5).to_a
end end

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
@@ -43,7 +43,7 @@ class AboutClasses < EdgeCase::Koan
fido = Dog2.new fido = Dog2.new
fido.set_name("Fido") fido.set_name("Fido")
assert_equal __, fido.instance_variable_get("@name") assert_equal __, fido.instance_variable_get("@name")
end end
def test_you_can_rip_the_value_out_using_instance_eval def test_you_can_rip_the_value_out_using_instance_eval
@@ -89,7 +89,7 @@ class AboutClasses < EdgeCase::Koan
assert_equal __, fido.name assert_equal __, fido.name
end end
# ------------------------------------------------------------------ # ------------------------------------------------------------------
class Dog5 class Dog5
@@ -125,12 +125,12 @@ class AboutClasses < EdgeCase::Koan
# THINK ABOUT IT: # THINK ABOUT IT:
# Why is this so? # Why is this so?
end end
def test_different_objects_have_difference_instance_variables def test_different_objects_have_difference_instance_variables
fido = Dog6.new("Fido") fido = Dog6.new("Fido")
rover = Dog6.new("Rover") rover = Dog6.new("Rover")
assert_not_equal rover.name, fido.name assert_equal __, rover.name != fido.name
end end
# ------------------------------------------------------------------ # ------------------------------------------------------------------
@@ -164,12 +164,12 @@ class AboutClasses < EdgeCase::Koan
def test_to_s_provides_a_string_version_of_the_object def test_to_s_provides_a_string_version_of_the_object
fido = Dog7.new("Fido") fido = Dog7.new("Fido")
assert_equal "Fido", fido.to_s assert_equal __, fido.to_s
end end
def test_to_s_is_used_in_string_interpolation def test_to_s_is_used_in_string_interpolation
fido = Dog7.new("Fido") fido = Dog7.new("Fido")
assert_equal "My dog is Fido", "My dog is #{fido}" assert_equal __, "My dog is #{fido}"
end end
def test_inspect_provides_a_more_complete_string_version def test_inspect_provides_a_more_complete_string_version
@@ -186,5 +186,5 @@ class AboutClasses < EdgeCase::Koan
assert_equal __, "STRING".to_s assert_equal __, "STRING".to_s
assert_equal __, "STRING".inspect assert_equal __, "STRING".inspect
end end
end end

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,13 +1,12 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class DiceSet # Implement a DiceSet Class here:
attr_reader :values #
def roll(n) # class DiceSet
@values = (1..n).map { rand(6) + 1 } # code ...
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
@@ -22,12 +22,12 @@ class AboutExceptions < EdgeCase::Koan
assert_equal __, result assert_equal __, result
assert ex.is_a?(StandardError), "Failure message." assert_equal __, ex.is_a?(StandardError), "Should be a Standard Error"
assert ex.is_a?(RuntimeError), "Failure message." assert_equal __, ex.is_a?(RuntimeError), "Should be a Runtime Error"
assert RuntimeError.ancestors.include?(StandardError), assert RuntimeError.ancestors.include?(StandardError),
"RuntimeError is a subclass of StandardError" "RuntimeError is a subclass of StandardError"
assert_equal __, ex.message assert_equal __, ex.message
end end
@@ -57,4 +57,12 @@ class AboutExceptions < EdgeCase::Koan
assert_equal __, result assert_equal __, result
end end
# Sometimes, we must know about the unknown
def test_asserting_an_error_is_raised
# A do-end is a block, a topic to explore more later
assert_raise(___) do
raise MySpecialError.new("New instances can be raised directly.")
end
end
end end

View File

@@ -1,9 +1,9 @@
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
empty_hash = Hash.new empty_hash = Hash.new
assert_equal Hash, empty_hash.class assert_equal __, empty_hash.class
assert_equal({}, empty_hash) assert_equal({}, empty_hash)
assert_equal __, empty_hash.size assert_equal __, empty_hash.size
end end
@@ -25,7 +25,7 @@ class AboutHashes < EdgeCase::Koan
hash[:one] = "eins" hash[:one] = "eins"
expected = { :one => __, :two => "dos" } expected = { :one => __, :two => "dos" }
assert_equal expected, hash assert_equal __, expected == hash
# Bonus Question: Why was "expected" broken out into a variable # Bonus Question: Why was "expected" broken out into a variable
# rather than used as a literal? # rather than used as a literal?
@@ -35,7 +35,7 @@ class AboutHashes < EdgeCase::Koan
hash1 = { :one => "uno", :two => "dos" } hash1 = { :one => "uno", :two => "dos" }
hash2 = { :two => "dos", :one => "uno" } hash2 = { :two => "dos", :one => "uno" }
assert_equal hash1, hash2 assert_equal __, hash1 == hash2
end end
def test_hash_keys def test_hash_keys
@@ -43,24 +43,24 @@ class AboutHashes < EdgeCase::Koan
assert_equal __, hash.keys.size assert_equal __, hash.keys.size
assert_equal __, hash.keys.include?(:one) assert_equal __, hash.keys.include?(:one)
assert_equal __, hash.keys.include?(:two) assert_equal __, hash.keys.include?(:two)
assert_equal Array, hash.keys.class assert_equal __, hash.keys.class
end end
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 __, hash.values.class
end end
def test_combining_hashes def test_combining_hashes
hash = { "jim" => 53, "amy" => 20, "dan" => 23 } hash = { "jim" => 53, "amy" => 20, "dan" => 23 }
new_hash = hash.merge({ "jim" => 54, "jenny" => 26 }) new_hash = hash.merge({ "jim" => 54, "jenny" => 26 })
assert_not_equal hash, new_hash assert_equal __, hash != new_hash
expected = { "jim" => __, "amy" => 20, "dan" => 23, "jenny" => __ } expected = { "jim" => __, "amy" => 20, "dan" => 23, "jenny" => __ }
assert_equal expected, new_hash assert_equal __, expected == new_hash
end end
end end

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
@@ -12,7 +12,7 @@ class AboutIteration < EdgeCase::Koan
array.each do |item| array.each do |item|
sum += item sum += item
end end
assert_equal 6, sum assert_equal __, sum
end end
def test_each_can_use_curly_brace_blocks_too def test_each_can_use_curly_brace_blocks_too
@@ -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

137
koans/about_java_interop.rb Normal file
View File

@@ -0,0 +1,137 @@
require File.expand_path(File.dirname(__FILE__) + '/edgecase')
include Java
# Concepts
# * Pull in a java class
# * calling a method, Camel vs snake
# * Resovling module/class name conflicts
# * Showing what gets returned
# * Ruby Strings VS Java Strings
# * Calling custom java class
# * Calling Ruby from java???
class AboutJavaInterop < EdgeCase::Koan
def test_using_a_java_library_class
java_array = java.util.ArrayList.new
assert_equal __, java_array.class
end
def test_java_class_can_be_referenced_using_both_ruby_and_java_like_syntax
assert_equal __, Java::JavaUtil::ArrayList == java.util.ArrayList
end
def test_include_class_includes_class_in_module_scope
assert_nil defined?(TreeSet)
include_class "java.util.TreeSet"
assert_equal __, defined?(TreeSet)
end
# THINK ABOUT IT:
#
# What if we use:
#
# include_class "java.lang.String"
#
# What would be the value of the String constant after this
# include_class is run? Would it be useful to provide a way of
# aliasing java classes to different names?
JString = java.lang.String
def test_also_java_class_can_be_given_ruby_aliases
java_string = JString.new("A Java String")
assert_equal __, java_string.class
assert_equal __, JString
end
def test_can_directly_call_java_methods_on_java_objects
java_string = JString.new("A Java String")
assert_equal __, java_string.toLowerCase
end
def test_jruby_provides_snake_case_versions_of_java_methods
java_string = JString.new("A Java String")
assert_equal __, java_string.to_lower_case
end
def test_jruby_provides_question_mark_versions_of_boolean_methods
java_string = JString.new("A Java String")
assert_equal __, java_string.endsWith("String")
assert_equal __, java_string.ends_with("String")
assert_equal __, java_string.ends_with?("String")
end
def test_java_string_are_not_ruby_strings
ruby_string = "A Java String"
java_string = java.lang.String.new(ruby_string)
assert_equal __, java_string.is_a?(java.lang.String)
assert_equal __, java_string.is_a?(String)
end
def test_java_strings_can_be_compared_to_ruby_strings_maybe
ruby_string = "A Java String"
java_string = java.lang.String.new(ruby_string)
assert_equal __, ruby_string == java_string
assert_equal __, java_string == ruby_string
# THINK ABOUT IT:
#
# Is there any possible way for this to be more wrong?
#
# SERIOUSLY, THINK ABOUT IT:
#
# Why do you suppose that Ruby and Java strings compare like that?
#
# ADVANCED THINK ABOUT IT:
#
# Is there a way to make Ruby/Java string comparisons commutative?
# How would you do it?
end
def test_however_most_methods_returning_strings_return_ruby_strings
java_array = java.util.ArrayList.new
assert_equal __, java_array.toString
assert_equal __, java_array.toString.is_a?(String)
assert_equal __, java_array.toString.is_a?(java.lang.String)
end
def test_some_ruby_objects_can_be_coerced_to_java
assert_equal __, "ruby string".to_java.class
assert_equal __, 1.to_java.class
assert_equal __, 9.32.to_java.class
assert_equal __, false.to_java.class
end
def test_some_ruby_objects_are_not_coerced_to_what_you_might_expect
assert_equal __, [].to_java.class == Java::JavaUtil::ArrayList
assert_equal __, {}.to_java.class == Java::JavaUtil::HashMap
assert_equal __, Object.new.to_java.class == Java::JavaLang::Object
end
def test_java_collections_are_enumerable
java_array = java.util.ArrayList.new
java_array << "one" << "two" << "three"
assert_equal __, java_array.map { |item| item.upcase }
end
# ------------------------------------------------------------------
# Open the Java ArrayList class and add a new method.
class Java::JavaUtil::ArrayList
def multiply_all
result = 1
each do |item|
result *= item
end
result
end
end
def test_java_class_are_open_from_ruby
java_array = java.util.ArrayList.new
java_array.add_all([1,2,3,4,5])
assert_equal __, java_array.multiply_all
end
end

View File

@@ -1,28 +1,28 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutMessagePassing < EdgeCase::Koan class AboutMessagePassing < EdgeCase::Koan
class MessageCatcher class MessageCatcher
def caught? def caught?
true true
end end
end end
def test_methods_can_be_called_directly def test_methods_can_be_called_directly
mc = MessageCatcher.new mc = MessageCatcher.new
assert mc.caught? assert mc.caught?
end end
def test_methods_can_be_invoked_by_sending_the_message def test_methods_can_be_invoked_by_sending_the_message
mc = MessageCatcher.new mc = MessageCatcher.new
assert mc.send(:caught?) assert mc.send(:caught?)
end end
def test_methods_can_be_invoked_more_dynamically def test_methods_can_be_invoked_more_dynamically
mc = MessageCatcher.new mc = MessageCatcher.new
assert mc.send("caught?") assert mc.send("caught?")
assert mc.send("caught" + __ ) # What do you need to add to the first string? assert mc.send("caught" + __ ) # What do you need to add to the first string?
assert mc.send("CAUGHT?".____ ) # What would you need to do to the string? assert mc.send("CAUGHT?".____ ) # What would you need to do to the string?
@@ -40,23 +40,22 @@ class AboutMessagePassing < EdgeCase::Koan
def test_classes_can_be_asked_if_they_know_how_to_respond def test_classes_can_be_asked_if_they_know_how_to_respond
mc = MessageCatcher.new mc = MessageCatcher.new
assert_equal __, mc.respond_to?(:caught?) assert_equal __, mc.respond_to?(:caught?)
assert_equal __, mc.respond_to?(:does_not_exist) assert_equal __, mc.respond_to?(:does_not_exist)
end end
# ------------------------------------------------------------------ # ------------------------------------------------------------------
class MessageCatcher class MessageCatcher
def add_a_payload(*args) def add_a_payload(*args)
return :empty unless args
args args
end end
end end
def test_sending_a_message_with_arguments def test_sending_a_message_with_arguments
mc = MessageCatcher.new mc = MessageCatcher.new
assert_equal __, mc.add_a_payload assert_equal __, mc.add_a_payload
assert_equal __, mc.send(:add_a_payload) assert_equal __, mc.send(:add_a_payload)
@@ -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,23 +1,23 @@
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
end end
class AboutMethods < EdgeCase::Koan class AboutMethods < EdgeCase::Koan
def test_calling_global_methods def test_calling_global_methods
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:
@@ -29,7 +29,7 @@ class AboutMethods < EdgeCase::Koan
# Rewrite the eval string to continue. # Rewrite the eval string to continue.
# #
end end
# NOTE: wrong number of argument is not a SYNTAX error, but a # NOTE: wrong number of argument is not a SYNTAX error, but a
# runtime error. # runtime error.
def test_calling_global_methods_with_wrong_number_of_arguments def test_calling_global_methods_with_wrong_number_of_arguments
@@ -135,7 +135,7 @@ class AboutMethods < EdgeCase::Koan
"tail" "tail"
end end
end end
def test_calling_methods_in_other_objects_require_explicit_receiver def test_calling_methods_in_other_objects_require_explicit_receiver
rover = Dog.new rover = Dog.new
assert_equal __, rover.name assert_equal __, rover.name

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
@@ -45,7 +45,7 @@ class AboutModules < EdgeCase::Koan
def test_module_methods_are_also_availble_in_the_object def test_module_methods_are_also_availble_in_the_object
fido = Dog.new fido = Dog.new
assert_nothing_raised(Exception) do assert_nothing_raised(Exception) do
fido.set_name("Rover") fido.set_name("Rover")
end end
end end

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

56
koans/about_objects.rb Normal file
View File

@@ -0,0 +1,56 @@
require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutObjects < EdgeCase::Koan
def test_everything_is_an_object
assert_equal __, 1.is_a?(Object)
assert_equal __, 1.5.is_a?(Object)
assert_equal __, "string".is_a?(Object)
assert_equal __, nil.is_a?(Object)
assert_equal __, Object.is_a?(Object)
end
def test_objects_can_be_converted_to_strings
assert_equal __, 123.to_s
assert_equal __, nil.to_s
end
def test_objects_can_be_inspected
assert_equal __, 123.inspect
assert_equal __, nil.inspect
end
def test_every_object_has_an_id
obj = Object.new
assert_equal __, obj.object_id.class
end
def test_every_object_has_different_id
obj = Object.new
another_obj = Object.new
assert_equal __, obj.object_id != another_obj.object_id
end
def test_some_system_objects_always_have_the_same_id
assert_equal __, false.object_id
assert_equal __, true.object_id
assert_equal __, nil.object_id
end
def test_small_integers_have_fixed_ids
assert_equal __, 0.object_id
assert_equal __, 1.object_id
assert_equal __, 2.object_id
assert_equal __, 100.object_id
# THINK ABOUT IT:
# What pattern do the object IDs for small integers follow?
end
def test_clone_creates_a_different_object
obj = Object.new
copy = obj.clone
assert_equal __, obj != copy
assert_equal __, obj.object_id != copy.object_id
end
end

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 __, /pattern/.class
end
def test_a_regexp_can_search_a_string_for_matching_content
assert_equal __, "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)
@@ -86,7 +86,7 @@ class AboutUsingBlocks < EdgeCase::Koan
def test_finding_lines2 def test_finding_lines2
assert_equal __, find_line2("example_file.txt") assert_equal __, find_line2("example_file.txt")
end end
# ------------------------------------------------------------------ # ------------------------------------------------------------------
def count_lines3(file_name) def count_lines3(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
@@ -28,9 +28,9 @@ class AboutScope < EdgeCase::Koan
rover = Joes::Dog.new rover = Joes::Dog.new
assert_equal __, fido.identify assert_equal __, fido.identify
assert_equal __, rover.identify assert_equal __, rover.identify
assert_not_equal fido.class, rover.class assert_equal __, fido.class != rover.class
assert_not_equal Jims::Dog, Joes::Dog assert_equal __, Jims::Dog != Joes::Dog
end end
# ------------------------------------------------------------------ # ------------------------------------------------------------------
@@ -41,7 +41,7 @@ class AboutScope < EdgeCase::Koan
def test_bare_bones_class_names_assume_the_current_scope def test_bare_bones_class_names_assume_the_current_scope
assert_equal __, AboutScope::String == String assert_equal __, AboutScope::String == String
end end
def test_nested_string_is_not_the_same_as_the_system_string def test_nested_string_is_not_the_same_as_the_system_string
assert_equal __, String == "HI".class assert_equal __, String == "HI".class
end end
@@ -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
@@ -7,7 +7,7 @@ require 'edgecase'
# A greed roll is scored as follows: # A greed roll is scored as follows:
# #
# * A set of three ones is 1000 points # * A set of three ones is 1000 points
# #
# * A set of three numbers (other than ones) is worth 100 times the # * A set of three numbers (other than ones) is worth 100 times the
# number. (e.g. three fives is 500 points). # number. (e.g. three fives is 500 points).
# #
@@ -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,12 +174,20 @@ 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
words = ["Now", "is", "the", "time"] words = ["Now", "is", "the", "time"]
assert_equal __, words.join(" ") assert_equal __, words.join(" ")
end end
def test_strings_are_not_unique_objects
a = "a string"
b = "a string"
assert_equal __, a == b
assert_equal __, a.object_id == b.object_id
end
end end

100
koans/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 __, symbol.is_a?(Symbol)
end
def test_symbols_can_be_compared
symbol1 = :a_symbol
symbol2 = :a_symbol
symbol3 = :something_else
assert_equal __, symbol1 == symbol2
assert_equal __, symbol1 == symbol3
end
def test_identical_symbols_are_a_single_internal_object
symbol1 = :a_symbol
symbol2 = :a_symbol
assert_equal __, symbol1 == symbol2
assert_equal __, symbol1.object_id == symbol2.object_id
end
def test_method_names_become_symbols
symbols_as_strings = Symbol.all_symbols.map { |x| x.to_s }
assert_equal __, symbols_as_strings.include?("test_method_names_become_symbols")
end
# THINK ABOUT IT:
#
# Why do we convert the list of symbols to strings and then compare
# against the string value rather than against symbols?
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 __, all_symbols.include?(__)
end
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_symbols_with_interpolation_can_be_built
value = "and"
symbol = :"cats #{value} 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
def test_symbols_can_be_dynamically_created
assert_equal __, ("cats" + "dogs").to_sym
end
# THINK ABOUT IT:
#
# Why is it not a good idea to dynamically create a lot of symbols?
end

54
koans/about_to_str.rb Normal file
View File

@@ -0,0 +1,54 @@
require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutToStr < EdgeCase::Koan
class CanNotBeTreatedAsString
def to_s
"non-string-like"
end
end
def test_to_s_returns_a_string_representation
not_like_a_string = CanNotBeTreatedAsString.new
assert_equal __, not_like_a_string.to_s
end
def test_normally_objects_cannot_be_used_where_strings_are_expected
assert_raise(___) do
File.exist?(CanNotBeTreatedAsString.new)
end
end
# ------------------------------------------------------------------
class CanBeTreatedAsString
def to_s
"string-like"
end
def to_str
to_s
end
end
def test_to_str_also_returns_a_string_representation
like_a_string = CanBeTreatedAsString.new
assert_equal __, like_a_string.to_str
end
def test_to_str_allows_objects_to_be_treated_as_strings
assert_equal __, File.exist?(CanBeTreatedAsString.new)
end
# ------------------------------------------------------------------
def acts_like_a_string?(string)
string = string.to_str if string.respond_to?(:to_str)
string.is_a?(String)
end
def test_user_defined_code_can_check_for_to_str
assert_equal __, acts_like_a_string?(CanNotBeTreatedAsString.new)
assert_equal __, acts_like_a_string?(CanBeTreatedAsString.new)
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)
@@ -22,4 +22,4 @@ class AboutTriangleAssignment < EdgeCase::Koan
assert_equal :scalene, triangle(5, 4, 2) assert_equal :scalene, triangle(5, 4, 2)
end end
end end

View File

@@ -1,15 +1,16 @@
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(2, 4, 2) end assert_raise(TriangleError) do triangle(1, 1, 3) 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

@@ -3,45 +3,177 @@
require 'test/unit/assertions' require 'test/unit/assertions'
# --------------------------------------------------------------------
# Support code for the Ruby Koans.
# --------------------------------------------------------------------
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 end
def in_ruby_version(*versions)
yield if versions.any? { |v| ruby_version?(v) }
end
# Standard, generic replacement value.
# If value19 is given, it is used inplace of value for Ruby 1.9.
def __(value="FILL ME IN", value19=:mu)
if RUBY_VERSION < "1.9"
value
else
(value19 == :mu) ? value : value19
end
end
# Numeric replacement value.
def _n_(value=999999, value19=:mu)
if RUBY_VERSION < "1.9"
value
else
(value19 == :mu) ? value : value19
end
end
# Error object replacement value.
def ___(value=FillMeInError) def ___(value=FillMeInError)
value value
end end
# Method name replacement.
class Object class Object
def ____(method=nil) def ____(method=nil)
if method if method
self.send(method) self.send(method)
end end
end end
in_ruby_version("1.9") do
public :method_missing
end
end
class String
def side_padding(width)
extra = width - self.size
if width < 0
self
else
left_padding = extra / 2
right_padding = (extra+1)/2
(" " * left_padding) + self + (" " *right_padding)
end
end
end end
module EdgeCase module EdgeCase
class Sensei class << self
attr_reader :failure, :failed_test def simple_output
ENV['SIMPLE_KOAN_OUTPUT'] == 'true'
end
end
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 use_colors?
color(color_value) + string + color(COLORS[:clear])
else
string
end
end
def color(color_value)
"\e[#{color_value}m"
end
def use_colors?
return false if ENV['NO_COLOR']
if ENV['ANSI_COLOR'].nil?
! using_windows?
else
ENV['ANSI_COLOR'] =~ /^(t|y)/i
end
end
def using_windows?
File::ALT_SEPARATOR
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,62 +186,175 @@ 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
if EdgeCase.simple_output
boring_end_screen
else
artistic_end_screen
end
end
def boring_end_screen
puts "Mountains are again merely mountains"
end
def artistic_end_screen
"JRuby 1.9.x Koans"
ruby_version = "(in #{'J' if defined?(JRUBY_VERSION)}Ruby #{defined?(JRUBY_VERSION) ? JRUBY_VERSION : RUBY_VERSION})"
ruby_version = ruby_version.side_padding(54)
completed = <<-ENDTEXT
,, , ,,
: ::::, :::,
, ,,: :::::::::::::,, :::: : ,
, ,,, ,:::::::::::::::::::, ,: ,: ,,
:, ::, , , :, ,::::::::::::::::::, ::: ,::::
: : ::, ,:::::::: ::, ,::::
, ,::::: :,:::::::,::::,
,: , ,:,,: :::::::::::::
::,: ,,:::, ,::::::::::::,
,:::, :,,::: ::::::::::::,
,::: :::::::, Mountains are again merely mountains ,::::::::::::
:::,,,:::::: ::::::::::::
,:::::::::::, ::::::::::::,
:::::::::::, ,::::::::::::
::::::::::::: ,::::::::::::
:::::::::::: Ruby Koans ::::::::::::,
::::::::::::#{ ruby_version },::::::::::::,
:::::::::::, , ::::::::::::
,:::::::::::::, 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)
backtrace.reject { |line| backtrace.reject { |line|
line =~ /test\/unit\/|edgecase\.rb/ line =~ /test\/unit\/|edgecase\.rb|minitest/
} }
end end
# 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 +371,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 +394,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
@@ -177,7 +413,7 @@ module EdgeCase
load(arg) load(arg)
else else
fail "Unknown command line argument '#{arg}'" fail "Unknown command line argument '#{arg}'"
end end
end end
end end
end end
@@ -200,17 +436,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,18 @@
# 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_objects'
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'
@@ -25,4 +31,8 @@ require 'about_scope'
require 'about_class_methods' require 'about_class_methods'
require 'about_message_passing' require 'about_message_passing'
require 'about_proxy_object_project' require 'about_proxy_object_project'
require 'about_to_str'
in_ruby_version("jruby") do
require 'about_java_interop'
end
require 'about_extra_credit' require 'about_extra_credit'

33
rakelib/checks.rake Normal file
View File

@@ -0,0 +1,33 @@
namespace "check" do
desc "Check that the require files match the about_* files"
task :abouts do
about_files = Dir['src/about_*.rb'].size
about_requires = `grep require src/path_to_enlightenment.rb | wc -l`.to_i
puts "Checking path_to_enlightenment completeness"
puts "# of about files: #{about_files}"
puts "# of about requires: #{about_requires}"
if about_files > about_requires
puts "*** There seems to be requires missing in the path to enlightenment"
else
puts "OK"
end
puts
end
desc "Check that asserts have __ replacements"
task :asserts do
puts "Checking for asserts missing the replacement text:"
begin
sh "egrep -n 'assert( |_)' src/about_* | egrep -v '__|_n_|project|about_assert' | egrep -v ' *#'"
puts
puts "Examine the above lines for missing __ replacements"
rescue RuntimeError => ex
puts "OK"
end
puts
end
end
desc "Run some simple consistancy checks"
task :check => ["check:abouts", "check:asserts"]

8
rakelib/run.rake Normal file
View File

@@ -0,0 +1,8 @@
RUBIES = ENV['KOAN_RUBIES'] || %w(ruby-1.8.7-p299,ruby-1.9.2-p0,jruby-1.5.2,jruby-head)
task :runall do
chdir('src') do
ENV['SIMPLE_KOAN_OUTPUT'] = 'true'
sh "rvm #{RUBIES} path_to_enlightenment.rb"
end
end

View File

@@ -1,11 +1,11 @@
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
names = ["John", "Smith"] names = ["John", "Smith"]
assert_equal __(["John", "Smith"]), names assert_equal __(["John", "Smith"]), names
end end
def test_parallel_assignments def test_parallel_assignments
first_name, last_name = ["John", "Smith"] first_name, last_name = ["John", "Smith"]
assert_equal __("John"), first_name assert_equal __("John"), first_name
@@ -18,13 +18,19 @@ class AboutArrayAssignment < EdgeCase::Koan
assert_equal __("Smith"), last_name assert_equal __("Smith"), last_name
end end
def test_parallel_assignments_with_extra_variables def test_parallel_assignments_with_splat_operator
first_name, *last_name = ["John", "Smith", "III"]
assert_equal __("John"), first_name
assert_equal __(["Smith","III"]), last_name
end
def test_parallel_assignments_with_too_few_variables
first_name, last_name = ["Cher"] first_name, last_name = ["Cher"]
assert_equal __("Cher"), first_name assert_equal __("Cher"), first_name
assert_equal __(nil), last_name assert_equal __(nil), last_name
end end
def test_parallel_assignements_with_subarrays def test_parallel_assignments_with_subarrays
first_name, last_name = [["Willie", "Rae"], "Johnson"] first_name, last_name = [["Willie", "Rae"], "Johnson"]
assert_equal __(["Willie", "Rae"]), first_name assert_equal __(["Willie", "Rae"]), first_name
assert_equal __("Johnson"), last_name assert_equal __("Johnson"), last_name
@@ -35,4 +41,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,18 +1,18 @@
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
empty_array = Array.new empty_array = Array.new
assert_equal Array, empty_array.class assert_equal __(Array), empty_array.class
assert_equal __(0), empty_array.size assert_equal __(0), empty_array.size
end end
def test_array_literals def test_array_literals
array = Array.new array = Array.new
assert_equal [], array assert_equal [], array # __
array[0] = 1 array[0] = 1
assert_equal [1], array assert_equal [1], array # __
array[1] = 2 array[1] = 2
assert_equal [1, __(2)], array assert_equal [1, __(2)], array
@@ -45,9 +45,9 @@ class AboutArrays < EdgeCase::Koan
end end
def test_arrays_and_ranges def test_arrays_and_ranges
assert_equal Range, (1..5).class assert_equal __(Range), (1..5).class
assert_not_equal [1,2,3,4,5], (1..5) assert_not_equal [1,2,3,4,5], (1..5) # __
assert_equal [1,2,3,4,5], (1..5).to_a assert_equal __([1,2,3,4,5]), (1..5).to_a
assert_equal __([1,2,3,4]), (1...5).to_a assert_equal __([1,2,3,4]), (1...5).to_a
end end

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
@@ -43,7 +43,7 @@ class AboutClasses < EdgeCase::Koan
fido = Dog2.new fido = Dog2.new
fido.set_name("Fido") fido.set_name("Fido")
assert_equal __("Fido"), fido.instance_variable_get("@name") assert_equal __("Fido"), fido.instance_variable_get("@name")
end end
def test_you_can_rip_the_value_out_using_instance_eval def test_you_can_rip_the_value_out_using_instance_eval
@@ -89,7 +89,7 @@ class AboutClasses < EdgeCase::Koan
assert_equal __("Fido"), fido.name assert_equal __("Fido"), fido.name
end end
# ------------------------------------------------------------------ # ------------------------------------------------------------------
class Dog5 class Dog5
@@ -125,12 +125,12 @@ class AboutClasses < EdgeCase::Koan
# THINK ABOUT IT: # THINK ABOUT IT:
# Why is this so? # Why is this so?
end end
def test_different_objects_have_difference_instance_variables def test_different_objects_have_difference_instance_variables
fido = Dog6.new("Fido") fido = Dog6.new("Fido")
rover = Dog6.new("Rover") rover = Dog6.new("Rover")
assert_not_equal rover.name, fido.name assert_equal __(true), rover.name != fido.name
end end
# ------------------------------------------------------------------ # ------------------------------------------------------------------
@@ -164,12 +164,12 @@ class AboutClasses < EdgeCase::Koan
def test_to_s_provides_a_string_version_of_the_object def test_to_s_provides_a_string_version_of_the_object
fido = Dog7.new("Fido") fido = Dog7.new("Fido")
assert_equal "Fido", fido.to_s assert_equal __("Fido"), fido.to_s
end end
def test_to_s_is_used_in_string_interpolation def test_to_s_is_used_in_string_interpolation
fido = Dog7.new("Fido") fido = Dog7.new("Fido")
assert_equal "My dog is Fido", "My dog is #{fido}" assert_equal __("My dog is Fido"), "My dog is #{fido}"
end end
def test_inspect_provides_a_more_complete_string_version def test_inspect_provides_a_more_complete_string_version
@@ -180,11 +180,11 @@ 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
assert_equal __('"STRING"'), "STRING".inspect assert_equal __('"STRING"'), "STRING".inspect
end end
end end

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,5 +1,12 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
# Implement a DiceSet Class here:
#
# class DiceSet
# code ...
# end
#--
class DiceSet class DiceSet
attr_reader :values attr_reader :values
def roll(n) def roll(n)
@@ -7,7 +14,8 @@ 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
@@ -22,12 +22,12 @@ class AboutExceptions < EdgeCase::Koan
assert_equal __(:exception_handled), result assert_equal __(:exception_handled), result
assert ex.is_a?(StandardError), "Failure message." assert_equal __(true), ex.is_a?(StandardError), "Should be a Standard Error"
assert ex.is_a?(RuntimeError), "Failure message." assert_equal __(true), ex.is_a?(RuntimeError), "Should be a Runtime Error"
assert RuntimeError.ancestors.include?(StandardError), assert RuntimeError.ancestors.include?(StandardError), # __
"RuntimeError is a subclass of StandardError" "RuntimeError is a subclass of StandardError"
assert_equal __("Oops"), ex.message assert_equal __("Oops"), ex.message
end end
@@ -57,4 +57,12 @@ class AboutExceptions < EdgeCase::Koan
assert_equal __(:always_run), result assert_equal __(:always_run), result
end end
# Sometimes, we must know about the unknown
def test_asserting_an_error_is_raised # __
# A do-end is a block, a topic to explore more later
assert_raise(___(MySpecialError)) do
raise MySpecialError.new("New instances can be raised directly.")
end
end
end end

View File

@@ -1,10 +1,10 @@
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
empty_hash = Hash.new empty_hash = Hash.new
assert_equal Hash, empty_hash.class assert_equal __(Hash), empty_hash.class
assert_equal({}, empty_hash) assert_equal({}, empty_hash) # __
assert_equal __(0), empty_hash.size assert_equal __(0), empty_hash.size
end end
@@ -25,7 +25,7 @@ class AboutHashes < EdgeCase::Koan
hash[:one] = "eins" hash[:one] = "eins"
expected = { :one => __("eins"), :two => "dos" } expected = { :one => __("eins"), :two => "dos" }
assert_equal expected, hash assert_equal __(true), expected == hash
# Bonus Question: Why was "expected" broken out into a variable # Bonus Question: Why was "expected" broken out into a variable
# rather than used as a literal? # rather than used as a literal?
@@ -35,7 +35,7 @@ class AboutHashes < EdgeCase::Koan
hash1 = { :one => "uno", :two => "dos" } hash1 = { :one => "uno", :two => "dos" }
hash2 = { :two => "dos", :one => "uno" } hash2 = { :two => "dos", :one => "uno" }
assert_equal hash1, hash2 assert_equal __(true), hash1 == hash2
end end
def test_hash_keys def test_hash_keys
@@ -43,24 +43,24 @@ class AboutHashes < EdgeCase::Koan
assert_equal __(2), hash.keys.size assert_equal __(2), hash.keys.size
assert_equal __(true), hash.keys.include?(:one) assert_equal __(true), hash.keys.include?(:one)
assert_equal __(true), hash.keys.include?(:two) assert_equal __(true), hash.keys.include?(:two)
assert_equal Array, hash.keys.class assert_equal __(Array), hash.keys.class
end end
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
end end
def test_combining_hashes def test_combining_hashes
hash = { "jim" => 53, "amy" => 20, "dan" => 23 } hash = { "jim" => 53, "amy" => 20, "dan" => 23 }
new_hash = hash.merge({ "jim" => 54, "jenny" => 26 }) new_hash = hash.merge({ "jim" => 54, "jenny" => 26 })
assert_not_equal hash, new_hash assert_equal __(true), hash != new_hash
expected = { "jim" => __(54), "amy" => 20, "dan" => 23, "jenny" => __(26) } expected = { "jim" => __(54), "amy" => 20, "dan" => 23, "jenny" => __(26) }
assert_equal expected, new_hash assert_equal __(true), expected == new_hash
end end
end end

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
@@ -12,7 +12,7 @@ class AboutIteration < EdgeCase::Koan
array.each do |item| array.each do |item|
sum += item sum += item
end end
assert_equal 6, sum assert_equal __(6), sum
end end
def test_each_can_use_curly_brace_blocks_too def test_each_can_use_curly_brace_blocks_too
@@ -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

137
src/about_java_interop.rb Normal file
View File

@@ -0,0 +1,137 @@
require File.expand_path(File.dirname(__FILE__) + '/edgecase')
include Java
# Concepts
# * Pull in a java class
# * calling a method, Camel vs snake
# * Resovling module/class name conflicts
# * Showing what gets returned
# * Ruby Strings VS Java Strings
# * Calling custom java class
# * Calling Ruby from java???
class AboutJavaInterop < EdgeCase::Koan
def test_using_a_java_library_class
java_array = java.util.ArrayList.new
assert_equal __(Java::JavaUtil::ArrayList), java_array.class
end
def test_java_class_can_be_referenced_using_both_ruby_and_java_like_syntax
assert_equal __(true), Java::JavaUtil::ArrayList == java.util.ArrayList
end
def test_include_class_includes_class_in_module_scope
assert_nil defined?(TreeSet) # __
include_class "java.util.TreeSet"
assert_equal __("constant"), defined?(TreeSet)
end
# THINK ABOUT IT:
#
# What if we use:
#
# include_class "java.lang.String"
#
# What would be the value of the String constant after this
# include_class is run? Would it be useful to provide a way of
# aliasing java classes to different names?
JString = java.lang.String
def test_also_java_class_can_be_given_ruby_aliases
java_string = JString.new("A Java String")
assert_equal __(java.lang.String), java_string.class
assert_equal __(java.lang.String), JString
end
def test_can_directly_call_java_methods_on_java_objects
java_string = JString.new("A Java String")
assert_equal __("a java string"), java_string.toLowerCase
end
def test_jruby_provides_snake_case_versions_of_java_methods
java_string = JString.new("A Java String")
assert_equal __("a java string"), java_string.to_lower_case
end
def test_jruby_provides_question_mark_versions_of_boolean_methods
java_string = JString.new("A Java String")
assert_equal __(true), java_string.endsWith("String")
assert_equal __(true), java_string.ends_with("String")
assert_equal __(true), java_string.ends_with?("String")
end
def test_java_string_are_not_ruby_strings
ruby_string = "A Java String"
java_string = java.lang.String.new(ruby_string)
assert_equal __(true), java_string.is_a?(java.lang.String)
assert_equal __(false), java_string.is_a?(String)
end
def test_java_strings_can_be_compared_to_ruby_strings_maybe
ruby_string = "A Java String"
java_string = java.lang.String.new(ruby_string)
assert_equal __(false), ruby_string == java_string
assert_equal __(true), java_string == ruby_string
# THINK ABOUT IT:
#
# Is there any possible way for this to be more wrong?
#
# SERIOUSLY, THINK ABOUT IT:
#
# Why do you suppose that Ruby and Java strings compare like that?
#
# ADVANCED THINK ABOUT IT:
#
# Is there a way to make Ruby/Java string comparisons commutative?
# How would you do it?
end
def test_however_most_methods_returning_strings_return_ruby_strings
java_array = java.util.ArrayList.new
assert_equal __("[]"), java_array.toString
assert_equal __(true), java_array.toString.is_a?(String)
assert_equal __(false), java_array.toString.is_a?(java.lang.String)
end
def test_some_ruby_objects_can_be_coerced_to_java
assert_equal __(Java::JavaLang::String), "ruby string".to_java.class
assert_equal __(Java::JavaLang::Long), 1.to_java.class
assert_equal __(Java::JavaLang::Double), 9.32.to_java.class
assert_equal __(Java::JavaLang::Boolean), false.to_java.class
end
def test_some_ruby_objects_are_not_coerced_to_what_you_might_expect
assert_equal __(false), [].to_java.class == Java::JavaUtil::ArrayList
assert_equal __(false), {}.to_java.class == Java::JavaUtil::HashMap
assert_equal __(false), Object.new.to_java.class == Java::JavaLang::Object
end
def test_java_collections_are_enumerable
java_array = java.util.ArrayList.new
java_array << "one" << "two" << "three"
assert_equal __(["ONE", "TWO", "THREE"]), java_array.map { |item| item.upcase }
end
# ------------------------------------------------------------------
# Open the Java ArrayList class and add a new method.
class Java::JavaUtil::ArrayList
def multiply_all
result = 1
each do |item|
result *= item
end
result
end
end
def test_java_class_are_open_from_ruby
java_array = java.util.ArrayList.new
java_array.add_all([1,2,3,4,5])
assert_equal __(120), java_array.multiply_all
end
end

View File

@@ -1,29 +1,29 @@
require 'edgecase' require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutMessagePassing < EdgeCase::Koan class AboutMessagePassing < EdgeCase::Koan
class MessageCatcher class MessageCatcher
def caught? def caught?
true true
end end
end end
def test_methods_can_be_called_directly def test_methods_can_be_called_directly
mc = MessageCatcher.new mc = MessageCatcher.new
assert mc.caught? assert mc.caught? # __
end end
def test_methods_can_be_invoked_by_sending_the_message def test_methods_can_be_invoked_by_sending_the_message
mc = MessageCatcher.new mc = MessageCatcher.new
assert mc.send(:caught?) assert mc.send(:caught?) # __
end end
def test_methods_can_be_invoked_more_dynamically def test_methods_can_be_invoked_more_dynamically
mc = MessageCatcher.new mc = MessageCatcher.new
assert mc.send("caught?") assert mc.send("caught?") # __
assert mc.send("caught" + __("?") ) # What do you need to add to the first string? assert mc.send("caught" + __("?") ) # What do you need to add to the first string?
assert mc.send("CAUGHT?".____(:downcase) ) # What would you need to do to the string? assert mc.send("CAUGHT?".____(:downcase) ) # What would you need to do to the string?
end end
@@ -40,23 +40,22 @@ class AboutMessagePassing < EdgeCase::Koan
def test_classes_can_be_asked_if_they_know_how_to_respond def test_classes_can_be_asked_if_they_know_how_to_respond
mc = MessageCatcher.new mc = MessageCatcher.new
assert_equal __(true), mc.respond_to?(:caught?) assert_equal __(true), mc.respond_to?(:caught?)
assert_equal __(false), mc.respond_to?(:does_not_exist) assert_equal __(false), mc.respond_to?(:does_not_exist)
end end
# ------------------------------------------------------------------ # ------------------------------------------------------------------
class MessageCatcher class MessageCatcher
def add_a_payload(*args) def add_a_payload(*args)
return :empty unless args
args args
end end
end end
def test_sending_a_message_with_arguments def test_sending_a_message_with_arguments
mc = MessageCatcher.new mc = MessageCatcher.new
assert_equal __([]), mc.add_a_payload assert_equal __([]), mc.add_a_payload
assert_equal __([]), mc.send(:add_a_payload) assert_equal __([]), mc.send(:add_a_payload)
@@ -75,7 +74,7 @@ class AboutMessagePassing < EdgeCase::Koan
exception = assert_raise(___(NoMethodError)) do exception = assert_raise(___(NoMethodError)) do
typical.foobar typical.foobar
end end
assert_match(/foobar/, exception.message) assert_match(/foobar/, exception.message) # __
end end
def test_calling_method_missing_causes_the_no_method_error def test_calling_method_missing_causes_the_no_method_error
@@ -84,12 +83,24 @@ class AboutMessagePassing < EdgeCase::Koan
exception = assert_raise(___(NoMethodError)) do exception = assert_raise(___(NoMethodError)) do
typical.method_missing(:foobar) typical.method_missing(:foobar)
end end
assert_match(/foobar/, exception.message) assert_match(/foobar/, exception.message) # __
# THINK ABOUT IT: # THINK ABOUT IT:
# #
# 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
# ------------------------------------------------------------------ # ------------------------------------------------------------------
@@ -111,7 +122,7 @@ class AboutMessagePassing < EdgeCase::Koan
def test_catching_messages_makes_respond_to_lie def test_catching_messages_makes_respond_to_lie
catcher = AllMessageCatcher.new catcher = AllMessageCatcher.new
assert_nothing_raised(NoMethodError) do assert_nothing_raised(NoMethodError) do # __
catcher.any_method catcher.any_method
end end
assert_equal __(false), catcher.respond_to?(:any_method) assert_equal __(false), catcher.respond_to?(:any_method)

View File

@@ -1,28 +1,28 @@
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
end end
class AboutMethods < EdgeCase::Koan class AboutMethods < EdgeCase::Koan
def test_calling_global_methods def test_calling_global_methods
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
#++ #++
eval "assert_equal 5, my_global_method 2, 3" # ENABLE CHECK eval "assert_equal 5, my_global_method 2, 3" # ENABLE CHECK # __
#-- #--
end end
#++ #++
@@ -36,19 +36,22 @@ class AboutMethods < EdgeCase::Koan
# Rewrite the eval string to continue. # Rewrite the eval string to continue.
# #
end end
# NOTE: wrong number of argument is not a SYNTAX error, but a # NOTE: wrong number of argument is not a SYNTAX error, but a
# runtime error. # runtime error.
def test_calling_global_methods_with_wrong_number_of_arguments def test_calling_global_methods_with_wrong_number_of_arguments
exception = assert_raise(___(ArgumentError)) do exception = assert_raise(___(ArgumentError)) do
my_global_method my_global_method
end end
assert_match(/#{__("wrong number of arguments")}/, exception.message) #--
pattern = "wrong (number|#) of arguments"
#++
assert_match(/#{__(pattern)}/, 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(/#{__(pattern)}/, exception.message)
end end
# ------------------------------------------------------------------ # ------------------------------------------------------------------
@@ -142,7 +145,7 @@ class AboutMethods < EdgeCase::Koan
"tail" "tail"
end end
end end
def test_calling_methods_in_other_objects_require_explicit_receiver def test_calling_methods_in_other_objects_require_explicit_receiver
rover = Dog.new rover = Dog.new
assert_equal __("Fido"), rover.name assert_equal __("Fido"), rover.name

View File

@@ -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
@@ -44,8 +44,8 @@ class AboutModules < EdgeCase::Koan
def test_module_methods_are_also_availble_in_the_object def test_module_methods_are_also_availble_in_the_object
fido = Dog.new fido = Dog.new
assert_nothing_raised(Exception) do assert_nothing_raised(Exception) do # __
fido.set_name("Rover") fido.set_name("Rover")
end end
end end

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

56
src/about_objects.rb Normal file
View File

@@ -0,0 +1,56 @@
require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutObjects < EdgeCase::Koan
def test_everything_is_an_object
assert_equal __(true), 1.is_a?(Object)
assert_equal __(true), 1.5.is_a?(Object)
assert_equal __(true), "string".is_a?(Object)
assert_equal __(true), nil.is_a?(Object)
assert_equal __(true), Object.is_a?(Object)
end
def test_objects_can_be_converted_to_strings
assert_equal __("123"), 123.to_s
assert_equal __(""), nil.to_s
end
def test_objects_can_be_inspected
assert_equal __("123"), 123.inspect
assert_equal __("nil"), nil.inspect
end
def test_every_object_has_an_id
obj = Object.new
assert_equal __(Fixnum), obj.object_id.class
end
def test_every_object_has_different_id
obj = Object.new
another_obj = Object.new
assert_equal __(true), obj.object_id != another_obj.object_id
end
def test_some_system_objects_always_have_the_same_id
assert_equal __(0), false.object_id
assert_equal __(2), true.object_id
assert_equal __(4), nil.object_id
end
def test_small_integers_have_fixed_ids
assert_equal __(1), 0.object_id
assert_equal __(3), 1.object_id
assert_equal __(5), 2.object_id
assert_equal __(201), 100.object_id
# THINK ABOUT IT:
# What pattern do the object IDs for small integers follow?
end
def test_clone_creates_a_different_object
obj = Object.new
copy = obj.clone
assert_equal __(true), obj != copy
assert_equal __(true), obj.object_id != copy.object_id
end
end

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)
@@ -93,7 +93,7 @@ class AboutUsingBlocks < EdgeCase::Koan
def test_finding_lines2 def test_finding_lines2
assert_equal __("test\n"), find_line2("example_file.txt") assert_equal __("test\n"), find_line2("example_file.txt")
end end
# ------------------------------------------------------------------ # ------------------------------------------------------------------
def count_lines3(file_name) def count_lines3(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
@@ -28,9 +28,9 @@ class AboutScope < EdgeCase::Koan
rover = Joes::Dog.new rover = Joes::Dog.new
assert_equal __(:jims_dog), fido.identify assert_equal __(:jims_dog), fido.identify
assert_equal __(:joes_dog), rover.identify assert_equal __(:joes_dog), rover.identify
assert_not_equal fido.class, rover.class assert_equal __(true), fido.class != rover.class
assert_not_equal Jims::Dog, Joes::Dog assert_equal __(true), Jims::Dog != Joes::Dog
end end
# ------------------------------------------------------------------ # ------------------------------------------------------------------
@@ -41,7 +41,7 @@ class AboutScope < EdgeCase::Koan
def test_bare_bones_class_names_assume_the_current_scope def test_bare_bones_class_names_assume_the_current_scope
assert_equal __(true), AboutScope::String == String assert_equal __(true), AboutScope::String == String
end end
def test_nested_string_is_not_the_same_as_the_system_string def test_nested_string_is_not_the_same_as_the_system_string
assert_equal __(false), String == "HI".class assert_equal __(false), String == "HI".class
end end
@@ -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
@@ -7,7 +7,7 @@ require 'edgecase'
# A greed roll is scored as follows: # A greed roll is scored as follows:
# #
# * A set of three ones is 1000 points # * A set of three ones is 1000 points
# #
# * A set of three numbers (other than ones) is worth 100 times the # * A set of three numbers (other than ones) is worth 100 times the
# number. (e.g. three fives is 500 points). # number. (e.g. three fives is 500 points).
# #
@@ -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,12 +174,20 @@ 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
words = ["Now", "is", "the", "time"] words = ["Now", "is", "the", "time"]
assert_equal __("Now is the time"), words.join(" ") assert_equal __("Now is the time"), words.join(" ")
end end
def test_strings_are_not_unique_objects
a = "a string"
b = "a string"
assert_equal __(true), a == b
assert_equal __(false), a.object_id == b.object_id
end
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_equal __(true), symbol1 == symbol2
assert_equal __(false), symbol1 == symbol3
end
def test_identical_symbols_are_a_single_internal_object
symbol1 = :a_symbol
symbol2 = :a_symbol
assert_equal __(true), symbol1 == symbol2
assert_equal __(true), symbol1.object_id == symbol2.object_id
end
def test_method_names_become_symbols
symbols_as_strings = Symbol.all_symbols.map { |x| x.to_s }
assert_equal __(true), symbols_as_strings.include?("test_method_names_become_symbols")
end
# THINK ABOUT IT:
#
# Why do we convert the list of symbols to strings and then compare
# against the string value rather than against symbols?
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_interpolation_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

54
src/about_to_str.rb Normal file
View File

@@ -0,0 +1,54 @@
require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutToStr < EdgeCase::Koan
class CanNotBeTreatedAsString
def to_s
"non-string-like"
end
end
def test_to_s_returns_a_string_representation
not_like_a_string = CanNotBeTreatedAsString.new
assert_equal __("non-string-like"), not_like_a_string.to_s
end
def test_normally_objects_cannot_be_used_where_strings_are_expected
assert_raise(___(TypeError)) do
File.exist?(CanNotBeTreatedAsString.new)
end
end
# ------------------------------------------------------------------
class CanBeTreatedAsString
def to_s
"string-like"
end
def to_str
to_s
end
end
def test_to_str_also_returns_a_string_representation
like_a_string = CanBeTreatedAsString.new
assert_equal __("string-like"), like_a_string.to_str
end
def test_to_str_allows_objects_to_be_treated_as_strings
assert_equal __(false), File.exist?(CanBeTreatedAsString.new)
end
# ------------------------------------------------------------------
def acts_like_a_string?(string)
string = string.to_str if string.respond_to?(:to_str)
string.is_a?(String)
end
def test_user_defined_code_can_check_for_to_str
assert_equal __(false), acts_like_a_string?(CanNotBeTreatedAsString.new)
assert_equal __(true), acts_like_a_string?(CanBeTreatedAsString.new)
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)
@@ -22,4 +22,4 @@ class AboutTriangleAssignment < EdgeCase::Koan
assert_equal :scalene, triangle(5, 4, 2) assert_equal :scalene, triangle(5, 4, 2)
end end
end end

View File

@@ -1,15 +1,16 @@
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(2, 4, 2) end assert_raise(TriangleError) do triangle(1, 1, 3) 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

@@ -3,45 +3,177 @@
require 'test/unit/assertions' require 'test/unit/assertions'
# --------------------------------------------------------------------
# Support code for the Ruby Koans.
# --------------------------------------------------------------------
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 end
def in_ruby_version(*versions)
yield if versions.any? { |v| ruby_version?(v) }
end
# Standard, generic replacement value.
# If value19 is given, it is used inplace of value for Ruby 1.9.
def __(value="FILL ME IN", value19=:mu)
if RUBY_VERSION < "1.9"
value
else
(value19 == :mu) ? value : value19
end
end
# Numeric replacement value.
def _n_(value=999999, value19=:mu)
if RUBY_VERSION < "1.9"
value
else
(value19 == :mu) ? value : value19
end
end
# Error object replacement value.
def ___(value=FillMeInError) def ___(value=FillMeInError)
value value
end end
# Method name replacement.
class Object class Object
def ____(method=nil) def ____(method=nil)
if method if method
self.send(method) self.send(method)
end end
end end
in_ruby_version("1.9") do
public :method_missing
end
end
class String
def side_padding(width)
extra = width - self.size
if width < 0
self
else
left_padding = extra / 2
right_padding = (extra+1)/2
(" " * left_padding) + self + (" " *right_padding)
end
end
end end
module EdgeCase module EdgeCase
class Sensei class << self
attr_reader :failure, :failed_test def simple_output
ENV['SIMPLE_KOAN_OUTPUT'] == 'true'
end
end
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 use_colors?
color(color_value) + string + color(COLORS[:clear])
else
string
end
end
def color(color_value)
"\e[#{color_value}m"
end
def use_colors?
return false if ENV['NO_COLOR']
if ENV['ANSI_COLOR'].nil?
! using_windows?
else
ENV['ANSI_COLOR'] =~ /^(t|y)/i
end
end
def using_windows?
File::ALT_SEPARATOR
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,62 +186,175 @@ 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
if EdgeCase.simple_output
boring_end_screen
else
artistic_end_screen
end
end
def boring_end_screen
puts "Mountains are again merely mountains"
end
def artistic_end_screen
"JRuby 1.9.x Koans"
ruby_version = "(in #{'J' if defined?(JRUBY_VERSION)}Ruby #{defined?(JRUBY_VERSION) ? JRUBY_VERSION : RUBY_VERSION})"
ruby_version = ruby_version.side_padding(54)
completed = <<-ENDTEXT
,, , ,,
: ::::, :::,
, ,,: :::::::::::::,, :::: : ,
, ,,, ,:::::::::::::::::::, ,: ,: ,,
:, ::, , , :, ,::::::::::::::::::, ::: ,::::
: : ::, ,:::::::: ::, ,::::
, ,::::: :,:::::::,::::,
,: , ,:,,: :::::::::::::
::,: ,,:::, ,::::::::::::,
,:::, :,,::: ::::::::::::,
,::: :::::::, Mountains are again merely mountains ,::::::::::::
:::,,,:::::: ::::::::::::
,:::::::::::, ::::::::::::,
:::::::::::, ,::::::::::::
::::::::::::: ,::::::::::::
:::::::::::: Ruby Koans ::::::::::::,
::::::::::::#{ ruby_version },::::::::::::,
:::::::::::, , ::::::::::::
,:::::::::::::, 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)
backtrace.reject { |line| backtrace.reject { |line|
line =~ /test\/unit\/|edgecase\.rb/ line =~ /test\/unit\/|edgecase\.rb|minitest/
} }
end end
# 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 +371,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 +394,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
@@ -177,7 +413,7 @@ module EdgeCase
load(arg) load(arg)
else else
fail "Unknown command line argument '#{arg}'" fail "Unknown command line argument '#{arg}'"
end end
end end
end end
end end
@@ -200,17 +436,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,18 @@
# 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_objects'
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'
@@ -25,4 +31,8 @@ require 'about_scope'
require 'about_class_methods' require 'about_class_methods'
require 'about_message_passing' require 'about_message_passing'
require 'about_proxy_object_project' require 'about_proxy_object_project'
require 'about_to_str'
in_ruby_version("jruby") do
require 'about_java_interop'
end
require 'about_extra_credit' require 'about_extra_credit'