Browse Source

Initial Import

master
Peter J. Jones 5 years ago
commit
0ccd15f896
100 changed files with 3912 additions and 0 deletions
  1. 5
    0
      Gemfile
  2. 16
    0
      Gemfile.lock
  3. 33
    0
      LICENSE.md
  4. 143
    0
      README.md
  5. 25
    0
      Rakefile
  6. 225
    0
      assumptions/exceptions_test.rb
  7. 140
    0
      assumptions/meta_test.rb
  8. 265
    0
      assumptions/oop_test.rb
  9. 60
    0
      benchmarks/collections.rb
  10. 44
    0
      benchmarks/literal.rb
  11. 39
    0
      benchmarks/meta.rb
  12. 65
    0
      benchmarks/oop.rb
  13. 66
    0
      collections/array_test.rb
  14. 97
    0
      collections/delegation_test.rb
  15. 39
    0
      collections/dup_test.rb
  16. 89
    0
      collections/hashdef_test.rb
  17. 141
    0
      collections/reduce_test.rb
  18. 120
    0
      collections/set_test.rb
  19. 2
    0
      coverage/Gemfile
  20. 14
    0
      coverage/Gemfile.lock
  21. 14
    0
      coverage/Rakefile
  22. 25
    0
      coverage/test.rb
  23. 26
    0
      coverage/widget.rb
  24. 10
    0
      data/README.md
  25. 15
    0
      data/weather-dups.csv
  26. 11
    0
      data/weather.csv
  27. 22
    0
      data/wind-memory.txt
  28. 9
    0
      data/wind-profile.txt
  29. 11
    0
      data/wind-stack.txt
  30. 75
    0
      exceptions/ensure_return_test.rb
  31. 206
    0
      exceptions/resources_test.rb
  32. 117
    0
      exceptions/retry_test.rb
  33. 174
    0
      exceptions/specific_test.rb
  34. 128
    0
      exceptions/strings_test.rb
  35. 88
    0
      exceptions/throw_test.rb
  36. 22
    0
      fuzz/fuzz_uri.rb
  37. 19
    0
      irb/collections/arraymeth.sh
  38. 20
    0
      irb/collections/coldup.sh
  39. 20
    0
      irb/collections/deepdup.sh
  40. 21
    0
      irb/collections/hashdef1.sh
  41. 21
    0
      irb/collections/hashdef2.sh
  42. 22
    0
      irb/collections/hashdef3.sh
  43. 20
    0
      irb/collections/hashdef4.sh
  44. 20
    0
      irb/collections/hashdef5.sh
  45. 19
    0
      irb/collections/hashdef6.sh
  46. 18
    0
      irb/collections/hashtoa.sh
  47. 20
    0
      irb/collections/subarray.sh
  48. 18
    0
      irb/collections/subarray2.sh
  49. 20
    0
      irb/collections/tuner1.sh
  50. 38
    0
      irb/common.sh
  51. 79
    0
      irb/irbrc.rb
  52. 19
    0
      irb/meta/arity.sh
  53. 23
    0
      irb/meta/binding.sh
  54. 24
    0
      irb/meta/blockargs.sh
  55. 19
    0
      irb/meta/blockproc.sh
  56. 21
    0
      irb/meta/blockweakstrong.sh
  57. 20
    0
      irb/meta/counterreset1.sh
  58. 19
    0
      irb/meta/counterreset2.sh
  59. 20
    0
      irb/meta/decorator1.sh
  60. 20
    0
      irb/meta/decorator2.sh
  61. 23
    0
      irb/meta/eval-widget1.sh
  62. 22
    0
      irb/meta/eval-widget2.sh
  63. 23
    0
      irb/meta/eval-widget3.sh
  64. 22
    0
      irb/meta/eval-widget4.sh
  65. 21
    0
      irb/meta/hashproxy1.sh
  66. 20
    0
      irb/meta/hashproxy2.sh
  67. 21
    0
      irb/meta/logmethod.sh
  68. 19
    0
      irb/meta/modinclude.sh
  69. 19
    0
      irb/meta/modprepend.sh
  70. 18
    0
      irb/meta/noinherit.sh
  71. 18
    0
      irb/meta/onlyspace1.sh
  72. 20
    0
      irb/meta/onlyspace2.sh
  73. 18
    0
      irb/oop/class-dispatch.sh
  74. 19
    0
      irb/oop/classceo.sh
  75. 19
    0
      irb/oop/classivar.sh
  76. 19
    0
      irb/oop/classvar.sh
  77. 20
    0
      irb/oop/color1.sh
  78. 20
    0
      irb/oop/color2.sh
  79. 20
    0
      irb/oop/equal.sh
  80. 19
    0
      irb/oop/module-dispatch.sh
  81. 19
    0
      irb/oop/numeq.sh
  82. 21
    0
      irb/oop/numeq2.sh
  83. 20
    0
      irb/oop/opcall.sh
  84. 20
    0
      irb/oop/parent-child-noargs.sh
  85. 19
    0
      irb/oop/parent-child-super.sh
  86. 20
    0
      irb/oop/regexceo.sh
  87. 18
    0
      irb/oop/setter-nospace.sh
  88. 19
    0
      irb/oop/setter.sh
  89. 20
    0
      irb/oop/spaceship.sh
  90. 17
    0
      irb/oop/super-happy.sh
  91. 20
    0
      irb/oop/superclasses.sh
  92. 19
    0
      irb/oop/ver1.sh
  93. 19
    0
      irb/oop/ver2.sh
  94. 21
    0
      irb/oop/ver3.sh
  95. 21
    0
      irb/performance/gcstat.sh
  96. 18
    0
      irb/ruby/const.sh
  97. 23
    0
      irb/ruby/false-eq.sh
  98. 18
    0
      irb/ruby/nil-tos.sh
  99. 19
    0
      irb/ruby/nil-tox.sh
  100. 0
    0
      irb/ruby/nomethod.sh

+ 5
- 0
Gemfile View File

@@ -0,0 +1,5 @@
source('https://rubygems.org')
gem('fuzzbert', '1.0.2')
gem('mrproper', '0.0.4')
gem('ruby-prof', '0.15.1')
gem('id3tag', '0.7.0')

+ 16
- 0
Gemfile.lock View File

@@ -0,0 +1,16 @@
GEM
remote: https://rubygems.org/
specs:
fuzzbert (1.0.2)
id3tag (0.7.0)
mrproper (0.0.4)
ruby-prof (0.15.1)

PLATFORMS
ruby

DEPENDENCIES
fuzzbert (= 1.0.2)
id3tag (= 0.7.0)
mrproper (= 0.0.4)
ruby-prof (= 0.15.1)

+ 33
- 0
LICENSE.md View File

@@ -0,0 +1,33 @@
# Copyright and Authors

Copyright (c) 2013, 2014 Peter J. Jones <pjones@devalot.com>

# License (BSD3)

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.

* Neither the name of Peter J. Jones nor the names of other
contributors may be used to endorse or promote products
derived from this software without specific prior written
permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 143
- 0
README.md View File

@@ -0,0 +1,143 @@
# Effective Ruby Source Code

Source code for the book [Effective Ruby][] by [Peter J. Jones] [].

## Introduction

Virtually all of the code from the book is implemented as unit tests
using `MiniTest`. I used a makefile and some scripts to convert the
book from Markdown to the format used by my publisher, Addison-Wesley.
During this process all of the tests are run and snippets of code are
automatically extracted from the tests and inserted into the final
document.

## Running the Tests

If you just want to run the tests against your currently installed
version of Ruby:

~~~
bundle install && rake test
~~~

On the other hand, if you have [rbenv] [] installed and want to test
against all the versions listed in the `ruby-versions.txt` file:

~~~
./runtests.sh
~~~

That script will install all of the necessary Ruby versions and then
test against each one of them.

## Issues and Pull Requests

There are several reasons you might want to open an issue:

* You get the tests to pass on a version or implementation of Ruby
not listed below. In that case, I'll update this file and
reference you and the issue.

* You get the tests to fail. Please provide as much detail in the
issue description. Worst case I'll update this file and reference
the issue and any workarounds.

* You don't like one of my examples or disagree with the approach
taken. Write a good issue description, include an alternate
approach, and if I like it I'll reference it in this file.

**Please do not submit pull requests.** If there ever happens to be a
new edition of the book then I don't want to have to track down every
contributor in order to sign a release. I'm lazy. Plus, come on,
this is just example code.

## Directory/Chapter Layout

The source code in this repository includes all of the code shown in
the book [Effective Ruby][] along with the snippets of code used to
produce the IRB sessions. It also includes several source code files
that did not appear in the book. First, let's take a look at the
directories that correspond to chapters in the book.

* `ruby`: Chapter 1: Accustoming Yourself to Ruby.

* `oop`: Chapter 2: Classes, Objects, and Modules.

* `collections`: Chapter 3: Collections.

* `exceptions`: Chapter 4: Exceptions.

* `meta`: Chapter 5: Metaprogramming.

* `testing`: Chapter 6: Testing.

* `tools`: Chapter 7: Tools and Libraries.

* `performance`: Chapter 8: Memory Management and Performance.

The remaining directories contain the following:

* `assumptions`: General tests created to ensure that statements
given in the book are accurate.

* `benchmarks`: Various benchmarks to compare the performance of
core classes or Ruby programming techniques.

* `coverage`: Example project for the test code coverage metrics
given in chapter 6.

* `data`: CSV files used by the tests along with profiling output
used in chapter 8.

* `fuzz`: Example of using FuzzBert from chapter 6.

* `irb`: All of the scripts used to generate the IRB output in the
book, organized by chapter.

* `lib`: Code used in the book for both examples and also by the IRB
scripts.

## Ruby Version

Unless otherwise noted, all of the code in this repository works with
the following Ruby versions:

* 1.9.3 (Official Ruby)
* 2.0.0 (Official Ruby)
* 2.1.0 (Official Ruby)
* 2.1.1 (Official Ruby)
* 2.1.2 (Official Ruby)

Where:

Official Ruby:
~ The original and [official][ruby-home] implementation of the Ruby
interpreter and language by the Ruby Core team (MRI).

Files that only work with a specific version of Ruby will have a
version specifier in their name. For example:

~~~
patching_2_1_test.rb
~~~

This file will only work with Ruby 2.1 or greater. The `Rakefile`
ensures that files are omitted from testing if the version of the Ruby
interpreter isn't appropriate.

## Contacting the Author

If you want to contact the author about the source code in this
repository please look at the *Issues and Pull Requests* section
above. For non-source code related topics, you can reach the author
through his [website][peter j. jones].

The author tweets Ruby tips at [@EffectiveRuby][] and personal junk at
[@contextualdev][].

[effective ruby]: http://www.effectiveruby.com/
[peter j. jones]: http://www.devalot.com/
[ruby-home]: http://www.ruby-lang.org/
[rbenv]: https://github.com/sstephenson/rbenv
[@effectiveruby]: https://twitter.com/EffectiveRuby
[@contextualdev]: https://twitter.com/contextualdev

+ 25
- 0
Rakefile View File

@@ -0,0 +1,25 @@
################################################################################
# This file is part of the package effrb. It is subject to the license
# terms in the LICENSE.md file found in the top-level directory of
# this distribution and at https://github.com/pjones/effrb. No part of
# the effrb package, including this file, may be copied, modified,
# propagated, or distributed except according to the terms contained
# in the LICENSE.md file.

################################################################################
require('rake/testtask')

################################################################################
task(:default => [:test])

################################################################################
Rake::TestTask.new do |t|
n,m,_ = RUBY_VERSION.split('.').map(&:to_i)

files = Dir.glob("*/*_test.rb")
files.reject! {|f| f.match(/2_0/)} unless n >= 2
files.reject! {|f| f.match(/2_1/)} unless n > 2 || (n == 2 && m >= 1)

t.test_files = files
t.warning = true
end

+ 225
- 0
assumptions/exceptions_test.rb View File

@@ -0,0 +1,225 @@
################################################################################
# This file is part of the package effrb. It is subject to the license
# terms in the LICENSE.md file found in the top-level directory of
# this distribution and at https://github.com/pjones/effrb. No part of
# the effrb package, including this file, may be copied, modified,
# propagated, or distributed except according to the terms contained
# in the LICENSE.md file.

################################################################################
require(File.expand_path('../lib/test.rb', File.dirname(__FILE__)))

################################################################################
class RescueAssumptionTests < MiniTest::Unit::TestCase

##############################################################################
class FakeError < StandardError; end

##############################################################################
def uninitialized_vars
x = 1
raise("stop")
y = 2
z = 3
ensure
return [x, y, z]
end

##############################################################################
def test_uninitialized_vars
vars = uninitialized_vars
assert_equal([1, nil, nil], vars)
end

##############################################################################
def return_exception
raise("1")
ensure
return $!.to_s
end

##############################################################################
def break_inside_ensure
%w(hello).each do |word|
begin
raise("WTF?")
ensure
break 2
end
end
end

##############################################################################
def next_inside_ensure
[3].each do |word|
begin
raise("WTF?")
ensure
next
end
end
end

##############################################################################
def nil_exception
return 4
ensure
return $!
end

##############################################################################
def exception_type
raise(Exception.new("foo"))
ensure
assert_equal(Exception, $!.class)
return 1
end

##############################################################################
def test_ensure_clause
assert_equal("1", return_exception)
assert_equal(2, break_inside_ensure)
assert_equal([3], next_inside_ensure)
assert_equal(nil, nil_exception)
assert_equal(1, exception_type)
end

##############################################################################
def return_in_rescue
raise("boom")
rescue
return 1
end

##############################################################################
def ensure_raise
return 1
rescue
return 2
ensure
raise("boom")
end

##############################################################################
def raise_because_no_method_error
nil.surely_i_dont_need_to_worry_about_this
ensure
return 1
end

##############################################################################
def complicated_redo
count = 2

[1,2,3].each do |n|
count -= 1
begin
raise("boom") if count > 0
ensure
redo if count > 0
end
end

return 1
end

##############################################################################
def complicated_flow
catch(:foo) do
[1, 2, 3].each do |n|
begin
raise("boom")
ensure
throw(:foo, 'hello')
end
end
end
end

##############################################################################
def order_matters
raise(RuntimeError)
rescue StandardError
return 1
rescue RuntimeError
return 2
end

##############################################################################
def return_runtime_error (x)
assert_equal(1, x)
RuntimeError
end

##############################################################################
def expressions_work
x = 1
raise(RuntimeError)
rescue return_runtime_error(x)
return 1
rescue StandardError
return 2
end

##############################################################################
def indirection_a
raise(FakeError)
rescue FakeError
return 1
end

##############################################################################
def indirection_b
raise(RuntimeError)
rescue RuntimeError
return indirection_a
end

##############################################################################
# Generates (SyntaxError)
# def retry_in_bad_place
# begin
# return 1
# ensure
# retry
# end
# end

##############################################################################
class Helper
attr_accessor(:ensure_called)
end

##############################################################################
def ensure_and_throw
helper = Helper.new
throw(:jump, helper)
ensure
helper.ensure_called = true
end

##############################################################################
def catch_helper
helper = catch(:jump) do
ensure_and_throw
nil
end

assert(helper)
assert(helper.ensure_called)
return 1
end

##############################################################################
def test_assumptions
assert_equal(1, return_in_rescue)
assert_raises(RuntimeError) {ensure_raise}
assert_equal(1, raise_because_no_method_error)
assert_equal(1, complicated_redo)
assert_equal('hello', complicated_flow)
assert_equal(1, order_matters)
assert_equal(1, expressions_work)
assert_equal(1, indirection_b)
assert_equal(1, catch_helper)
end
end

+ 140
- 0
assumptions/meta_test.rb View File

@@ -0,0 +1,140 @@
################################################################################
# This file is part of the package effrb. It is subject to the license
# terms in the LICENSE.md file found in the top-level directory of
# this distribution and at https://github.com/pjones/effrb. No part of
# the effrb package, including this file, may be copied, modified,
# propagated, or distributed except according to the terms contained
# in the LICENSE.md file.

################################################################################
require(File.expand_path('../lib/test.rb', File.dirname(__FILE__)))
require(File.expand_path('../lib/meta.rb', File.dirname(__FILE__)))

################################################################################
class MetaAssumptionTests < MiniTest::Unit::TestCase

##############################################################################
class InstanceMethodWatcher
def self.calls
@@calls ||= Hash.new(0)
end

def self.method_added (m); calls[:added] += 1; end
def self.method_removed (m); calls[:removed] =+ 1; end
def self.method_undefined (m); calls[:undef] += 1; end

def hello; end
remove_method(:hello)

def hello; end
undef_method(:hello)
end

##############################################################################
def test_instance_method_watcher
hash = InstanceMethodWatcher.calls
assert_equal(2, hash[:added])
assert_equal(1, hash[:removed])
assert_equal(1, hash[:undef])
end

##############################################################################
class SingletonMethodWatcher
def self.calls
@@calls ||= Hash.new(0)
end

def self.singleton_method_added (m); calls[:added] += 1; end
def self.singleton_method_removed (m); calls[:removed] =+ 1; end
def self.singleton_method_undefined (m); calls[:undef] += 1; end

def self.hello; end
class << self; remove_method(:hello); end

def self.hello; end
class << self; undef_method(:hello); end
end

##############################################################################
def test_singleton_method_watcher
hash = SingletonMethodWatcher.calls
assert_equal(5, hash[:added]) # watch out!
assert_equal(1, hash[:removed])
assert_equal(1, hash[:undef])
end

##############################################################################
class MissingDelegator
def initialize; @hash = {}; end
def respond_to_missing? (m, *); @hash.respond_to?(m); end
def method_missing (m, *a); @hash.send(m, *a); end
end

##############################################################################
def test_missing_delegator
d = MissingDelegator.new
assert(d.respond_to?(:assoc))
assert(!d.public_methods.include?(:assoc))
end

##############################################################################
class HookClassParent
def self.hook_class
@hook_class ||= nil
end

def self.method_added (m)
@hook_class = self
end
end

##############################################################################
class HookClassChild < HookClassParent
def foo; end
end

##############################################################################
def test_hook_class
assert_equal(HookClassChild, HookClassChild.hook_class)
end

##############################################################################
class DefineMethodWithBlock
define_method(:foo) {|*args, &block| block.call(*args)}
end

##############################################################################
def test_define_method_with_block
obj = DefineMethodWithBlock.new
str = "h"
obj.foo(str) {|x| x.upcase!}
assert_equal("H", str)
end

##############################################################################
def test_instance_eval_yields_self
widget = InstanceEvalWidget::Widget.new {|w| w.name = "foo"}
assert_equal("foo", widget.name)
end

##############################################################################
module IncludeA
def foo
end
end

module IncludeB
include(IncludeA)
end

class IncludeC
include(IncludeB)
extend(IncludeB)
end

##############################################################################
def test_methods_from_module_include
assert(IncludeC.public_instance_methods.include?(:foo))
assert(IncludeC.respond_to?(:foo))
end
end

+ 265
- 0
assumptions/oop_test.rb View File

@@ -0,0 +1,265 @@
################################################################################
# This file is part of the package effrb. It is subject to the license
# terms in the LICENSE.md file found in the top-level directory of
# this distribution and at https://github.com/pjones/effrb. No part of
# the effrb package, including this file, may be copied, modified,
# propagated, or distributed except according to the terms contained
# in the LICENSE.md file.

################################################################################
require(File.expand_path('../lib/test.rb', File.dirname(__FILE__)))

################################################################################
class OOPAssumptionTests < MiniTest::Unit::TestCase

##############################################################################
module ModA; def method_a () return :mod_a end; end
module ModB; def method_b () return :mod_b end; end

##############################################################################
class ClsA
# Bring in methods from a module before they are defined in the
# class. Order doesn't matter, but the tests below confirm for
# all recent versions of Ruby.
include(ModA)

def method_a
return :cls_a
end

def method_b
return :cls_a
end

# Bring in methods from a module after they have already been
# defined in the class. See note above.
include(ModB)
end

##############################################################################
def test_class_before_module
# Test that modules are one level above a class in the hierarchy.
cls_a = ClsA.new
assert_equal(:cls_a, cls_a.method_a)
assert_equal(:cls_a, cls_a.method_b)

# Make sure the methods are actually reachable.
cls_b = Class.new
cls_b.send(:include, ModA)
assert_equal(:mod_a, cls_b.new.method_a)
end

##############################################################################
class Parent
attr_accessor(:var)
def initialize (x=0) @var = x; end
end

##############################################################################
class ChildA < Parent
def initialize (x) super; end
end

##############################################################################
class ChildB < Parent
def initialize (x) super(x); end
end

##############################################################################
class ChildC < Parent
def initialize (x) super(); end
end

##############################################################################
def test_super_calls
assert_equal(1, ChildA.new(1).var)
assert_equal(1, ChildB.new(1).var)
assert_equal(0, ChildC.new(1).var)
end

##############################################################################
class ChildD < Parent
# no initialize.
end

##############################################################################
class SuperInMethodMissingError < StandardError; end

##############################################################################
class ChildE < ChildD
attr_reader(:foo_var)
def initialize (*args); super; end

def foo
@foo_var = :in_foo
super
end

def method_missing (m)
case m
when :foo, :bar
raise(SuperInMethodMissingError)
else
super
end
end
end

##############################################################################
class ChildF < ChildE
def bar
super
end
end

##############################################################################
def test_super_searches_up_more_than_one_level
assert_equal(1, ChildE.new(1).var)
end

##############################################################################
def test_super_to_method_missing
child_e = ChildE.new

assert_raises(SuperInMethodMissingError) {child_e.foo}
assert_equal(:in_foo, child_e.foo_var)

# method_missing from higher in the hierarchy works too.
assert_raises(SuperInMethodMissingError) {ChildF.new.bar}
end

##############################################################################
class BaseA
def method_a () :base_a; end
end

##############################################################################
class DerivedA
include(ModA)
def method_a () super; end
end

##############################################################################
def test_super_goes_to_mod_first
assert_equal(:mod_a, DerivedA.new.method_a)
end

##############################################################################
class BaseB
def method_1 (x=0, &block)
block.call(x)
end
def method_2 (x=0, &block)
block.call(x)
end
def method_3 (x=0, &block)
block.call(x)
end
def method_4 (x=0, &block)
block.call(x)
end
end

##############################################################################
class DerivedB < BaseB
def method_1 (x, &block)
super
end
def method_2 (x, &block)
super(&block)
end
def method_3 (x, &block)
super(x, &block)
end
def method_4 (x, &block)
super {|y| block.call(y)}
end
end

##############################################################################
def test_super_and_blocks
assert_equal(:foo, DerivedB.new.method_1(:foo) {|x| x})
assert_equal(0, DerivedB.new.method_2(:bar) {|x| x})
assert_equal(:bar, DerivedB.new.method_3(:bar) {|x| x})
assert_equal(:bar, DerivedB.new.method_4(:bar) {|x| x})
end

##############################################################################
class Setter
attr_reader(:foo)

def initialize (foo)
nowhitespace=(foo)
end

def nowhitespace= (foo)
@foo = foo
end
end

##############################################################################
def test_setter_class
assert_equal(nil, Setter.new(1).foo)
end

##############################################################################
module UnqualifiedA
module UnqualifiedB
FOO = 1

class Bar
def self.bar
UnqualifiedB::FOO
end
end
end
end

##############################################################################
def test_partially_qualified_names
assert_equal(UnqualifiedA::UnqualifiedB::FOO,
UnqualifiedA::UnqualifiedB::Bar.bar)
end

##############################################################################
class Protected1
protected
def foo () 1; end
end

##############################################################################
class Protected2 < Protected1
def go (other)
other.foo
end
end

##############################################################################
class Protected3 < Protected2
def go2 (other)
other.foo
end
end

##############################################################################
class Protected4 < Protected3
protected
def foo () 2; end
end

##############################################################################
def test_protected
p2 = Protected2.new
p3 = Protected3.new
p4 = Protected4.new

assert_equal(1, p2.go(p3))
assert_equal(1, p3.go(p2))
assert_equal(1, p3.go2(p2))
assert_raises(NoMethodError) {p2.go(p4)}

assert(p3.kind_of?(p2.class))
assert(!p2.kind_of?(p3.class))

end
end

+ 60
- 0
benchmarks/collections.rb View File

@@ -0,0 +1,60 @@
#!/usr/bin/env ruby -w

################################################################################
# This file is part of the package effrb. It is subject to the license
# terms in the LICENSE.md file found in the top-level directory of
# this distribution and at https://github.com/pjones/effrb. No part of
# the effrb package, including this file, may be copied, modified,
# propagated, or distributed except according to the terms contained
# in the LICENSE.md file.

################################################################################
require('benchmark')
require('set')

################################################################################
module FastestCollection

##############################################################################
ELEMENTS = 1_000_000
ITERATIONS = 100

##############################################################################
def self.array
(1..ELEMENTS).to_a.map(&:to_s)
end

##############################################################################
def self.hash
Hash[array.zip((1..ELEMENTS).to_a)]
end

##############################################################################
def self.range
1..ELEMENTS
end

##############################################################################
def self.set
Set.new(array)
end

##############################################################################
def self.run
array_ = array
hash_ = hash
range_ = range
set_ = set
key = (ELEMENTS - 1).to_s

Benchmark.bmbm do |bm|
bm.report(" array:") {ITERATIONS.times {array_.include?(key)}}
bm.report(" hash:") {ITERATIONS.times {hash_.include?(key)}}
bm.report(" range:") {ITERATIONS.times {range_.include?(ELEMENTS - 1)}}
bm.report(" set:") {ITERATIONS.times {set_.include?(key)}}
end
end
end

################################################################################
FastestCollection.run

+ 44
- 0
benchmarks/literal.rb View File

@@ -0,0 +1,44 @@
#!/usr/bin/env ruby -w

################################################################################
# This file is part of the package effrb. It is subject to the license
# terms in the LICENSE.md file found in the top-level directory of
# this distribution and at https://github.com/pjones/effrb. No part of
# the effrb package, including this file, may be copied, modified,
# propagated, or distributed except according to the terms contained
# in the LICENSE.md file.

################################################################################
class Worker

##############################################################################
LOOPS = 1_000

##############################################################################
def initialize (name, &block)
stat_a = GC.stat
LOOPS.times(&block)
stat_b = GC.stat

before = stat_a[:total_allocated_object] || 0
after = stat_b[:total_allocated_object] || 0

# The `times` method creates one object.
after -= 1 if after > 0

diff = (after - before).to_s.rjust(6, ' ')
$stdout.puts(name.rjust(15, ' ') + ': ' + diff)
end
end

Worker.new("nothing") {}
Worker.new("int") {1}
Worker.new("[int]") {[1, 2, 3]}
Worker.new("[int].freeze") {[1, 2, 3].freeze}
Worker.new("[str]") {%w(a b c)}
Worker.new("[str].freeze") {%w(a b c).freeze}
Worker.new("{int}") {{1 => 2}}
Worker.new("{int}.freeze") {{1 => 2}.freeze}
Worker.new("string") {"abc"}
Worker.new("string.freeze") {"abc".freeze}
Worker.new("string.freeze2") {"abc".freeze}

+ 39
- 0
benchmarks/meta.rb View File

@@ -0,0 +1,39 @@
#!/usr/bin/env ruby -w

################################################################################
# This file is part of the package effrb. It is subject to the license
# terms in the LICENSE.md file found in the top-level directory of
# this distribution and at https://github.com/pjones/effrb. No part of
# the effrb package, including this file, may be copied, modified,
# propagated, or distributed except according to the terms contained
# in the LICENSE.md file.

################################################################################
require('benchmark')
require('ostruct')

################################################################################
module MethodMissing

##############################################################################
ITERATIONS = 1_000_000

##############################################################################
class Silly
def one; 1; end
def method_missing (*); 2; end
end

##############################################################################
def self.run
obj = Silly.new

Benchmark.bmbm do |bm|
bm.report(" one:") {ITERATIONS.times {obj.one}}
bm.report(" two:") {ITERATIONS.times {obj.two}}
end
end
end

################################################################################
MethodMissing.run

+ 65
- 0
benchmarks/oop.rb View File

@@ -0,0 +1,65 @@
#!/usr/bin/env ruby -w

################################################################################
# This file is part of the package effrb. It is subject to the license
# terms in the LICENSE.md file found in the top-level directory of
# this distribution and at https://github.com/pjones/effrb. No part of
# the effrb package, including this file, may be copied, modified,
# propagated, or distributed except according to the terms contained
# in the LICENSE.md file.

################################################################################
require('benchmark')
require('ostruct')

################################################################################
module StructOrHash

##############################################################################
ITERATIONS = 1_000_000

##############################################################################
St = Struct.new(:a, :b)

##############################################################################
def self.struct
s = St.new(1, 2)

ITERATIONS.times do
s.a
s.b += 1
end
end

##############################################################################
def self.ostruct
o = OpenStruct.new(:a => 1, :b => 2)

ITERATIONS.times do
o.a
o.b += 1
end
end

##############################################################################
def self.hash
h = Hash[:a, 1, :b, 2] # Try to level the playing field with St.

ITERATIONS.times do
h[:a]
h[:b] += 1
end
end

##############################################################################
def self.run
Benchmark.bmbm do |bm|
bm.report(" struct:") {struct}
bm.report("ostruct:") {ostruct}
bm.report(" hash:") {hash}
end
end
end

################################################################################
StructOrHash.run

+ 66
- 0
collections/array_test.rb View File

@@ -0,0 +1,66 @@
################################################################################
# This file is part of the package effrb. It is subject to the license
# terms in the LICENSE.md file found in the top-level directory of
# this distribution and at https://github.com/pjones/effrb. No part of
# the effrb package, including this file, may be copied, modified,
# propagated, or distributed except according to the terms contained
# in the LICENSE.md file.

################################################################################
require(File.expand_path('../lib/test.rb', File.dirname(__FILE__)))

################################################################################
class ArrayTest < MiniTest::Unit::TestCase

##############################################################################
module Assumed
# <<: assumed-array
class Pizza
def initialize (toppings)
toppings.each do |topping|
add_and_price_topping(topping)
end
end

# ...
end
# :>>

class Pizza
attr_reader(:topping_count)
def add_and_price_topping (x)
@topping_count ||= 0
@topping_count += 1
end
end
end

module Forced
class Pizza < Assumed::Pizza; end

# <<: forced-array
class Pizza
def initialize (toppings)
Array(toppings).each do |topping|
add_and_price_topping(topping)
end
end

# ...
end
# :>>
end

##############################################################################
def test_assumed
assert_equal(1, Assumed::Pizza.new(["foo"]).topping_count)
assert_raises(NoMethodError) {Assumed::Pizza.new(nil)}
end

##############################################################################
def test_forced
assert_equal(1, Forced::Pizza.new(["foo"]).topping_count)
assert_equal(1, Forced::Pizza.new("foo").topping_count)
assert_equal(nil, Forced::Pizza.new(nil).topping_count)
end
end

+ 97
- 0
collections/delegation_test.rb View File

@@ -0,0 +1,97 @@
################################################################################
# This file is part of the package effrb. It is subject to the license
# terms in the LICENSE.md file found in the top-level directory of
# this distribution and at https://github.com/pjones/effrb. No part of
# the effrb package, including this file, may be copied, modified,
# propagated, or distributed except according to the terms contained
# in the LICENSE.md file.

################################################################################
require(File.expand_path('../lib/test.rb', File.dirname(__FILE__)))

################################################################################
class DelegationTest < MiniTest::Unit::TestCase

##############################################################################
# <<: raising-hash
require('forwardable')

class RaisingHash
extend(Forwardable)
include(Enumerable)

def_delegators(:@hash, :[], :[]=, :delete, :each,
:keys, :values, :length,
:empty?, :has_key?)
end
# :>>

##############################################################################
class RaisingHash
# <<: erase
# Forward self.erase! to @hash.delete
def_delegator(:@hash, :delete, :erase!)
# :>>
end

##############################################################################
class RaisingHash
# <<: initialize
def initialize
@hash = Hash.new do |hash, key|
raise(KeyError, "invalid key `#{key}'!")
end
end
# :>>

# <<: initialize-copy
def initialize_copy (other)
@hash = @hash.dup
end
# :>>

# <<: freeze
def freeze
@hash.freeze
super
end
# :>>

# <<: invert
def invert
other = self.class.new
other.replace!(@hash.invert)
other
end

protected

def replace! (hash)
hash.default_proc = @hash.default_proc
@hash = hash
end
# :>>
end

##############################################################################
def test_raising_hash
h = RaisingHash.new
h[1] = 2

assert_equal(2, h[1])
assert_equal(1, h.length)
assert_equal([1], h.keys)
assert_equal([2], h.values)
assert_raises(KeyError) {h[3]}
assert_raises(KeyError) {h.dup[3]}
assert_raises(KeyError) {h.invert[3]}

invoked = h.reduce(false) do |bool, (k,v)|
assert_equal(1, k)
assert_equal(2, v)
true
end

assert(invoked)
end
end

+ 39
- 0
collections/dup_test.rb View File

@@ -0,0 +1,39 @@
################################################################################
# This file is part of the package effrb. It is subject to the license
# terms in the LICENSE.md file found in the top-level directory of
# this distribution and at https://github.com/pjones/effrb. No part of
# the effrb package, including this file, may be copied, modified,
# propagated, or distributed except according to the terms contained
# in the LICENSE.md file.

################################################################################
require(File.expand_path('../lib/test.rb', File.dirname(__FILE__)))
require(File.expand_path('../lib/collections.rb', File.dirname(__FILE__)))

################################################################################
class DupTest < MiniTest::Unit::TestCase

##############################################################################
def test_phone_number
number = "1-800-CALL-MEEE"
pn = MutatePhone::PhoneNumber.new(number)
assert_equal('1800', number)
end

##############################################################################
def test_tuners
presets1 = %w(90.1 106.2 88.5)
presets2 = presets1.dup

t1 = MutateTuner::Tuner.new(presets1)
assert_equal(%w(90.1 88.5), presets1) # mutated

t2 = PersistentTuner::Tuner.new(presets2)
assert_equal(%w(90.1 106.2 88.5), presets2)
assert_equal(%w(90.1 88.5), t2.presets)

t3 = DupTuner::Tuner.new(presets2)
assert_equal(%w(90.1 106.2 88.5), presets2)
assert_equal(%w(90.1 88.5), t3.presets)
end
end

+ 89
- 0
collections/hashdef_test.rb View File

@@ -0,0 +1,89 @@
################################################################################
# This file is part of the package effrb. It is subject to the license
# terms in the LICENSE.md file found in the top-level directory of
# this distribution and at https://github.com/pjones/effrb. No part of
# the effrb package, including this file, may be copied, modified,
# propagated, or distributed except according to the terms contained
# in the LICENSE.md file.

################################################################################
require(File.expand_path('../lib/test.rb', File.dirname(__FILE__)))

################################################################################
class HashdefTest < MiniTest::Unit::TestCase

##############################################################################
module WithOrEq
# <<: frequency
def frequency (array)
array.reduce({}) do |hash, element|
hash[element] ||= 0 # Make sure the key exists.
hash[element] += 1 # Then increment it.
hash # Return the hash to reduce.
end
end
# :>>

module_function(:frequency)
end

##############################################################################
module WithDefault
# <<: frequency-def
def frequency (array)
array.reduce(Hash.new(0)) do |hash, element|
hash[element] += 1 # Increment the value.
hash # Return the hash to reduce.
end
end
# :>>

module_function(:frequency)
end

##############################################################################
def test_frequency
array = [1, 2, 1, 3]
hash = {1 => 2, 2 => 1, 3 => 1}

assert_equal(hash, WithOrEq.frequency(array))
assert_equal(hash, WithDefault.frequency(array))
end

##############################################################################
module Funcs
def self.short_eq
hash = Hash.new(0)
key = 1

# <<: expand-eq
# Short version:
hash[key] += 1

# Expands to:
hash[key] = hash[key] + 1
# :>>

hash
end

def self.bad_check
hash = {}
key = 1

# <<: bad-check
if hash[key]
# ...
end
# :>>

hash
end
end

##############################################################################
def test_funcs
assert_equal({1=>2}, Funcs.short_eq)
assert_equal({}, Funcs.bad_check)
end
end

+ 141
- 0
collections/reduce_test.rb View File

@@ -0,0 +1,141 @@
################################################################################
# This file is part of the package effrb. It is subject to the license
# terms in the LICENSE.md file found in the top-level directory of
# this distribution and at https://github.com/pjones/effrb. No part of
# the effrb package, including this file, may be copied, modified,
# propagated, or distributed except according to the terms contained
# in the LICENSE.md file.

################################################################################
require(File.expand_path('../lib/test.rb', File.dirname(__FILE__)))

################################################################################
class ReduceTest < MiniTest::Unit::TestCase

##############################################################################
module LongSum
# <<: long-sum
def sum (enum)
enum.reduce(0) do |accumulator, element|
accumulator + element
end
end
# :>>

module_function(:sum)
end

##############################################################################
module SumDefault
# <<: sum-default
def sum (enum)
enum.reduce do |accumulator, element|
accumulator + element
end
end
# :>>

module_function(:sum)
end

##############################################################################
module ShortSum
# <<: short-sum
def sum (enum)
enum.reduce(0, :+)
end
# :>>

module_function(:sum)
end

##############################################################################
def test_sum
enum = [1, 2, 3, 4, 5]
assert_equal(15, LongSum.sum(enum))
assert_equal(15, SumDefault.sum(enum))
assert_equal(15, ShortSum.sum(enum))
end

##############################################################################
module Transform

def self.array_to_hash_with_map (array)
# <<: array-map
Hash[array.map {|x| [x, true]}]
# :>>
end

def self.array_to_hash_with_reduce (array)
# <<: array-reduce
array.reduce({}) do |hash, element|
hash.update(element => true)
end
# :>>
end
end

##############################################################################
def test_transform
array = [1, 2, 3]
hash = {1 => true, 2 => true, 3 => true}

assert_equal(hash, Transform.array_to_hash_with_map(array))
assert_equal(hash, Transform.array_to_hash_with_reduce(array))
end

##############################################################################
module SelectMap
User = Struct.new(:age, :name)

def self.select_and_map (users)
# <<: user-select
users.select {|u| u.age >= 21}.map(&:name)
# :>>
end

def self.with_reduce (users)
# <<: user-reduce
users.reduce([]) do |names, user|
names << user.name if user.age >= 21
names
end
# :>>
end
end

##############################################################################
def test_select_map
users = [
SelectMap::User.new(15, "Jordan"),
SelectMap::User.new(88, "Orin"),
SelectMap::User.new(15, "Kyle"),
SelectMap::User.new(22, "Frank"),
]

assert_equal(%w(Orin Frank), SelectMap.select_and_map(users))
assert_equal(%w(Orin Frank), SelectMap.with_reduce(users))
end

##############################################################################
module BadFold
def self.convert (array)
# <<: bad-fold
hash = {}

array.each do |element|
hash[element] = true
end
# :>>

hash
end
end

##############################################################################
def test_bad_fold
array = [1, 2, 3]
hash = {1 => true, 2 => true, 3 => true}
assert_equal(hash, BadFold.convert(array))
end
end

+ 120
- 0
collections/set_test.rb View File

@@ -0,0 +1,120 @@
################################################################################
# This file is part of the package effrb. It is subject to the license
# terms in the LICENSE.md file found in the top-level directory of
# this distribution and at https://github.com/pjones/effrb. No part of
# the effrb package, including this file, may be copied, modified,
# propagated, or distributed except according to the terms contained
# in the LICENSE.md file.

################################################################################
require(File.expand_path('../lib/test.rb', File.dirname(__FILE__)))

################################################################################
class SetTest < MiniTest::Unit::TestCase

##############################################################################
DATA_FILE = File.expand_path('../data/weather-dups.csv', File.dirname(__FILE__))

##############################################################################
module UsingArray
# <<: array
class Role
def initialize (name, permissions)
@name, @permissions = name, permissions
end

def can? (permission)
@permissions.include?(permission)
end
end
# :>>
end

##############################################################################
module UsingHash
# <<: hash
class Role
def initialize (name, permissions)
@name = name
@permissions = Hash[permissions.map {|p| [p, true]}]
end

def can? (permission)
@permissions.include?(permission)
end
end
# :>>
end

##############################################################################
module UsingSet
# <<: set
require('set')

class Role
def initialize (name, permissions)
@name, @permissions = name, Set.new(permissions)
end

def can? (permission)
@permissions.include?(permission)
end
end
# :>>

# <<: weather
require('set')
require('csv')

class AnnualWeather
Reading = Struct.new(:date, :high, :low) do
def eql? (other) date.eql?(other.date); end
def hash; date.hash; end
end

def initialize (file_name)
@readings = Set.new

CSV.foreach(file_name, headers: true) do |row|
@readings << Reading.new(Date.parse(row[2]),
row[10].to_f,
row[11].to_f)
end
end
end
# :>>

class AnnualWeather
attr_reader(:readings)
def mean
return 0.0 if @readings.size.zero?

total = @readings.reduce(0.0) do |sum, reading|
sum + (reading.high + reading.low) / 2.0
end

total / @readings.size.to_f
end

end
end

##############################################################################
def test_role
[ UsingArray::Role,
UsingHash::Role,
UsingSet::Role,
].each do |klass|
role = klass.new(klass.to_s, [:one, :two, :three])
assert(role.can?(:two))
assert(!role.can?(:four))
end
end

##############################################################################
def test_weather
w = UsingSet::AnnualWeather.new(DATA_FILE)
assert_equal(10, w.readings.size)
assert_equal(53.4, w.mean)
end
end

+ 2
- 0
coverage/Gemfile View File

@@ -0,0 +1,2 @@
source('https://rubygems.org')
gem('simplecov', '~> 0.7.1')

+ 14
- 0
coverage/Gemfile.lock View File

@@ -0,0 +1,14 @@
GEM
remote: https://rubygems.org/
specs:
multi_json (1.9.2)
simplecov (0.7.1)
multi_json (~> 1.0)
simplecov-html (~> 0.7.1)
simplecov-html (0.7.1)

PLATFORMS
ruby

DEPENDENCIES
simplecov (~> 0.7.1)

+ 14
- 0
coverage/Rakefile View File

@@ -0,0 +1,14 @@
################################################################################
# This file is part of the package effrb. It is subject to the license
# terms in the LICENSE.md file found in the top-level directory of
# this distribution and at https://github.com/pjones/effrb. No part of
# the effrb package, including this file, may be copied, modified,
# propagated, or distributed except according to the terms contained
# in the LICENSE.md file.

require('rake/testtask')

Rake::TestTask.new do |t|
t.test_files = FileList['test.rb']
t.warning = true # Turn on Ruby warnings.
end

+ 25
- 0
coverage/test.rb View File

@@ -0,0 +1,25 @@
################################################################################
# This file is part of the package effrb. It is subject to the license
# terms in the LICENSE.md file found in the top-level directory of
# this distribution and at https://github.com/pjones/effrb. No part of
# the effrb package, including this file, may be copied, modified,
# propagated, or distributed except according to the terms contained
# in the LICENSE.md file.

################################################################################
require('simplecov')
SimpleCov.start

################################################################################
require('minitest/autorun')
require('./widget.rb')

################################################################################
class WidgetTest < MiniTest::Unit::TestCase

##############################################################################
def test_can_set_name
w = Widget.new("Foo")
assert_equal("Foo", w.name)
end
end

+ 26
- 0
coverage/widget.rb View File

@@ -0,0 +1,26 @@
################################################################################
# This file is part of the package effrb. It is subject to the license
# terms in the LICENSE.md file found in the top-level directory of
# this distribution and at https://github.com/pjones/effrb. No part of
# the effrb package, including this file, may be copied, modified,
# propagated, or distributed except according to the terms contained
# in the LICENSE.md file.

################################################################################
class Widget

attr_accessor(:name, :quantity)

def initialize (name)
@name = name
@quantity = 0
end

def inc
@quantity += 1
end

def dec
@quantity -= 1 if @quantity > 0
end
end

+ 10
- 0
data/README.md View File

@@ -0,0 +1,10 @@
# Test Data for Effective Ruby Examples

## Weather Data

The weather data was generated by the National Climatic Data Center
([NCDC][]), a division of the National Oceanic and Atmospheric
Administration ([NOAA][]).

[NCDC]: http://www.ncdc.noaa.gov/
[NOAA]: http://www.noaa.gov/

+ 15
- 0
data/weather-dups.csv View File

@@ -0,0 +1,15 @@
STATION,STATION_NAME,DATE,EMXP,MXSD,DSNW,TPCP,TSNW,EMXT,EMNT,MMXT,MMNT,MNTM
COOP:050848,BOULDER CO US,20130101,15,3,1,27,37,65,-4,46.2,19.7,33.0
COOP:050848,BOULDER CO US,20130101,15,3,1,27,37,65,-4,46.2,19.7,33.0
COOP:050848,BOULDER CO US,20130101,15,3,1,27,37,65,-4,46.2,19.7,33.0
COOP:050848,BOULDER CO US,20130201,41,10,3,112,185,61,8,44.6,19.6,32.1
COOP:050848,BOULDER CO US,20130301,82,9,4,172,228,76,3,54.0,27.0,40.5
COOP:050848,BOULDER CO US,20130401,71,11,8,413,476,77,2,57.7,29.8,43.7
COOP:050848,BOULDER CO US,20130501,132,9,2,266,123,87,17,71.7,43.6,57.6
COOP:050848,BOULDER CO US,20130501,132,9,2,266,123,87,17,71.7,43.6,57.6
COOP:050848,BOULDER CO US,20130501,132,9,2,266,123,87,17,71.7,43.6,57.6
COOP:050848,BOULDER CO US,20130601,37,0,0,61,0,98,40,86.2,53.5,69.8
COOP:050848,BOULDER CO US,20130701,23,0,0,103,0,98,51,86.8,57.5,72.2
COOP:050848,BOULDER CO US,20130801,27,0,0,140,0,97,50,87.6,56.7,72.2
COOP:050848,BOULDER CO US,20130901,908,0,0,1816,0,95,33,77.7,52.5,65.1
COOP:050848,BOULDER CO US,20131001,102,0,2,224,54,77,26,60.0,35.6,47.8

+ 11
- 0
data/weather.csv View File

@@ -0,0 +1,11 @@
STATION,STATION_NAME,DATE,EMXP,MXSD,DSNW,TPCP,TSNW,EMXT,EMNT,MMXT,MMNT,MNTM
COOP:050848,BOULDER CO US,20130101,15,3,1,27,37,65,-4,46.2,19.7,33.0
COOP:050848,BOULDER CO US,20130201,41,10,3,112,185,61,8,44.6,19.6,32.1
COOP:050848,BOULDER CO US,20130301,82,9,4,172,228,76,3,54.0,27.0,40.5
COOP:050848,BOULDER CO US,20130401,71,11,8,413,476,77,2,57.7,29.8,43.7
COOP:050848,BOULDER CO US,20130501,132,9,2,266,123,87,17,71.7,43.6,57.6
COOP:050848,BOULDER CO US,20130601,37,0,0,61,0,98,40,86.2,53.5,69.8
COOP:050848,BOULDER CO US,20130701,23,0,0,103,0,98,51,86.8,57.5,72.2
COOP:050848,BOULDER CO US,20130801,27,0,0,140,0,97,50,87.6,56.7,72.2
COOP:050848,BOULDER CO US,20130901,908,0,0,1816,0,95,33,77.7,52.5,65.1
COOP:050848,BOULDER CO US,20131001,102,0,2,224,54,77,26,60.0,35.6,47.8

+ 22
- 0
data/wind-memory.txt View File

@@ -0,0 +1,22 @@
Total allocated 892553
Total retained 4680

allocated memory by gem
-----------------------------------
2.1.1/lib x 75201418
rubygems x 1181583
other x 120

allocated memory by location
-----------------------------------
lib/ruby/2.1.0/net/protocol.rb:153 x 35241433
lib/ruby/2.1.0/csv.rb:1806 x 16217960
lib/ruby/2.1.0/csv.rb:1833 x 16217960
lib/ruby/2.1.0/csv.rb:1783 x 2918998

retained memory by location
-----------------------------------
lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55 x 116966
lib/ruby/2.1.0/uri/common.rb:853 x 20440
lib/ruby/2.1.0/x86_64-linux/socket.so:0 x 17676
lib/ruby/2.1.0/uri/common.rb:863 x 11680

+ 9
- 0
data/wind-profile.txt View File

@@ -0,0 +1,9 @@
% cumulative self self total
time seconds seconds calls ms/call ms/call name
62.60 15.70 3.92 433413 0.04 0.16 CSV#shift
10.13 18.24 2.54 14054 0.18 1.58 Array#each
4.39 19.34 1.10 405451 0.00 0.00 String#=~
3.79 20.29 0.95 405472 0.00 0.00 String#[]
3.51 21.17 0.88 420453 0.00 0.00 Hash#[]
3.27 21.99 0.82 405924 0.00 0.00 Array#<<
3.15 22.78 0.79 419475 0.00 0.00 String#==

+ 11
- 0
data/wind-stack.txt View File

@@ -0,0 +1,11 @@
94.91% CSV#each [ 1 calls]
-> 93.44% CSV#shift [ 13982 calls]
-> 91.27% Kernel#loop [ 13982 calls]
|> 9.47% String#=~ [405449 calls]
|> 8.00% String#[] [405449 calls]
|> 5.77% Array#<< [405449 calls]
|> 5.72% Hash#[] [419430 calls]
|> 5.54% String#empty? [419430 calls]
|> 5.40% String#== [405449 calls]
|> 2.56% String#split [ 13981 calls]
|> 1.20% String#sub! [ 13981 calls]

+ 75
- 0
exceptions/ensure_return_test.rb View File

@@ -0,0 +1,75 @@
################################################################################
# This file is part of the package effrb. It is subject to the license
# terms in the LICENSE.md file found in the top-level directory of
# this distribution and at https://github.com/pjones/effrb. No part of
# the effrb package, including this file, may be copied, modified,
# propagated, or distributed except according to the terms contained
# in the LICENSE.md file.

################################################################################
require(File.expand_path('../lib/test.rb', File.dirname(__FILE__)))

################################################################################
class EnsureTrickTest < MiniTest::Unit::TestCase

##############################################################################
class SpecificError < StandardError; end
class TooStrongError < StandardError; end

##############################################################################
# <<: return
def tricky
# ...
return 'horses'
ensure
return 'ponies'
end
# :>>

##############################################################################
# <<: explicit
def explicit
# ...
return 'horses'
rescue SpecificError => e
# Recover from the exception.
return 'ponies'
ensure
# Clean up only.
end
# :>>

##############################################################################
# <<: bare
def hammer
# ...
return 'hit'
rescue
# Discard exceptions.
return 'smash'
end
# :>>

##############################################################################
def obscure
items = %w(jasmine lilac lavender)

# <<: next
items.each do |item|
begin
raise TooStrongError if item == 'lilac'
ensure
next # Cancels exception, continues iteration.
end
end
# :>>
end

##############################################################################
def test_ensure
assert_equal('ponies', tricky)
assert_equal('horses', explicit)
assert_equal(%w(jasmine lilac lavender), obscure)
assert_equal('hit', hammer)
end
end

+ 206
- 0
exceptions/resources_test.rb View File

@@ -0,0 +1,206 @@
################################################################################
# This file is part of the package effrb. It is subject to the license
# terms in the LICENSE.md file found in the top-level directory of
# this distribution and at https://github.com/pjones/effrb. No part of
# the effrb package, including this file, may be copied, modified,
# propagated, or distributed except according to the terms contained
# in the LICENSE.md file.

################################################################################
require(File.expand_path('../lib/test.rb', File.dirname(__FILE__)))

################################################################################
class ResourcesTest < MiniTest::Unit::TestCase

##############################################################################
class FileNaive
def initialize (file_name)
# <<: naive
file = File.open(file_name, 'w')
# ...
file.close
# :>>
end
end

##############################################################################
def test_file_naive_is_created
file_name = 'some_file.txt'
assert(!File.exist?(file_name))
FileNaive.new(file_name)
assert(File.exist?(file_name))
File.unlink(file_name)
end

##############################################################################
class FileBegin
def initialize (file_name)
# <<: begin
begin
file = File.open(file_name, 'w')
# ...
ensure
file.close if file
end
# :>>
end
end

##############################################################################
# This is starting to look familiar.
def test_file_begin_is_created
file_name = 'some_file.txt'
assert(!File.exist?(file_name))
FileBegin.new(file_name)
assert(File.exist?(file_name))
File.unlink(file_name)
end

##############################################################################
class FileBlock
def initialize (file_name)
# <<: block
File.open(file_name, 'w') do |file|
# ...
end
# :>>
end
end

##############################################################################
# This is starting to look familiar.
def test_file_block_is_created
file_name = 'some_file.txt'
assert(!File.exist?(file_name))
FileBlock.new(file_name)
assert(File.exist?(file_name))
File.unlink(file_name)
end

##############################################################################
# <<: class
class Lock
def self.acquire
lock = new # Initialize the resource
lock.exclusive_lock!
yield(lock) # Give it to the block
ensure
# Make sure it gets unlocked.
lock.unlock if lock
end
end
# :>>

##############################################################################
# Add a few more things.
class Lock
attr_reader(:unlocked)
def unlock () @unlocked = true; end
def exclusive_lock! () end
end

##############################################################################
# <<: use
Lock.acquire do |lock|
# Raising an exception here is okay.
end
# :>>

##############################################################################
def test_yield_unlock_happy_path
yielded = false

resource = Lock.acquire do |r|
yielded = true
r
end

assert(yielded, "should have yielded")
assert(resource.unlocked, "should have unlocked")
end

##############################################################################
def test_yield_unlock_exception
checked = false
resource = nil

begin
Lock.acquire do |r|
resource = r
raise("blow up!")
end
rescue
checked = true
assert(!resource.nil?)
assert(resource.unlocked)
end

assert(checked)
end

##############################################################################
module OptionalYield

##############################################################################
# <<: class
class Lock
def self.acquire
lock = new # Initialize the resource.
lock.exclusive_lock!

if block_given?
yield(lock)
else
lock # Act more like Lock.new.
end
ensure
if block_given?
# Make sure it gets unlocked.
lock.unlock if lock
end
end
end
# :>>

##############################################################################
# Add a few more things.
class Lock
attr_reader(:unlocked)
def unlock () @unlocked = true; end
def exclusive_lock! () end
end

##############################################################################
def self.use_lock_with_block
# <<: use
Lock.acquire do |resource|
# Raising an exception here is okay.
end
# :>>
end