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 @@
1
+source('https://rubygems.org')
2
+gem('fuzzbert',  '1.0.2')
3
+gem('mrproper',  '0.0.4')
4
+gem('ruby-prof', '0.15.1')
5
+gem('id3tag',    '0.7.0')

+ 16
- 0
Gemfile.lock View File

@@ -0,0 +1,16 @@
1
+GEM
2
+  remote: https://rubygems.org/
3
+  specs:
4
+    fuzzbert (1.0.2)
5
+    id3tag (0.7.0)
6
+    mrproper (0.0.4)
7
+    ruby-prof (0.15.1)
8
+
9
+PLATFORMS
10
+  ruby
11
+
12
+DEPENDENCIES
13
+  fuzzbert (= 1.0.2)
14
+  id3tag (= 0.7.0)
15
+  mrproper (= 0.0.4)
16
+  ruby-prof (= 0.15.1)

+ 33
- 0
LICENSE.md View File

@@ -0,0 +1,33 @@
1
+# Copyright and Authors
2
+
3
+    Copyright (c) 2013, 2014 Peter J. Jones <pjones@devalot.com>
4
+
5
+# License (BSD3)
6
+
7
+    Redistribution and use in source and binary forms, with or without
8
+    modification, are permitted provided that the following conditions are met:
9
+
10
+        * Redistributions of source code must retain the above copyright
11
+          notice, this list of conditions and the following disclaimer.
12
+
13
+        * Redistributions in binary form must reproduce the above
14
+          copyright notice, this list of conditions and the following
15
+          disclaimer in the documentation and/or other materials provided
16
+          with the distribution.
17
+
18
+        * Neither the name of Peter J. Jones nor the names of other
19
+          contributors may be used to endorse or promote products
20
+          derived from this software without specific prior written
21
+          permission.
22
+
23
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24
+    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25
+    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26
+    A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27
+    OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28
+    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29
+    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
30
+    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
31
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32
+    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
33
+    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 143
- 0
README.md View File

@@ -0,0 +1,143 @@
1
+# Effective Ruby Source Code
2
+
3
+Source code for the book [Effective Ruby][] by [Peter J. Jones] [].
4
+
5
+## Introduction
6
+
7
+Virtually all of the code from the book is implemented as unit tests
8
+using `MiniTest`.  I used a makefile and some scripts to convert the
9
+book from Markdown to the format used by my publisher, Addison-Wesley.
10
+During this process all of the tests are run and snippets of code are
11
+automatically extracted from the tests and inserted into the final
12
+document.
13
+
14
+## Running the Tests
15
+
16
+If you just want to run the tests against your currently installed
17
+version of Ruby:
18
+
19
+~~~
20
+bundle install && rake test
21
+~~~
22
+
23
+On the other hand, if you have [rbenv] [] installed and want to test
24
+against all the versions listed in the `ruby-versions.txt` file:
25
+
26
+~~~
27
+./runtests.sh
28
+~~~
29
+
30
+That script will install all of the necessary Ruby versions and then
31
+test against each one of them.
32
+
33
+## Issues and Pull Requests
34
+
35
+There are several reasons you might want to open an issue:
36
+
37
+  * You get the tests to pass on a version or implementation of Ruby
38
+    not listed below.  In that case, I'll update this file and
39
+    reference you and the issue.
40
+
41
+  * You get the tests to fail.  Please provide as much detail in the
42
+    issue description.  Worst case I'll update this file and reference
43
+    the issue and any workarounds.
44
+
45
+  * You don't like one of my examples or disagree with the approach
46
+    taken.  Write a good issue description, include an alternate
47
+    approach, and if I like it I'll reference it in this file.
48
+
49
+**Please do not submit pull requests.** If there ever happens to be a
50
+new edition of the book then I don't want to have to track down every
51
+contributor in order to sign a release.  I'm lazy.  Plus, come on,
52
+this is just example code.
53
+
54
+## Directory/Chapter Layout
55
+
56
+The source code in this repository includes all of the code shown in
57
+the book [Effective Ruby][] along with the snippets of code used to
58
+produce the IRB sessions.  It also includes several source code files
59
+that did not appear in the book.  First, let's take a look at the
60
+directories that correspond to chapters in the book.
61
+
62
+  * `ruby`: Chapter 1: Accustoming Yourself to Ruby.
63
+
64
+  * `oop`: Chapter 2: Classes, Objects, and Modules.
65
+
66
+  * `collections`: Chapter 3: Collections.
67
+
68
+  * `exceptions`: Chapter 4: Exceptions.
69
+
70
+  * `meta`: Chapter 5: Metaprogramming.
71
+
72
+  * `testing`: Chapter 6: Testing.
73
+
74
+  * `tools`: Chapter 7: Tools and Libraries.
75
+
76
+  * `performance`: Chapter 8: Memory Management and Performance.
77
+
78
+The remaining directories contain the following:
79
+
80
+  * `assumptions`: General tests created to ensure that statements
81
+    given in the book are accurate.
82
+
83
+  * `benchmarks`: Various benchmarks to compare the performance of
84
+    core classes or Ruby programming techniques.
85
+
86
+  * `coverage`: Example project for the test code coverage metrics
87
+    given in chapter 6.
88
+
89
+  * `data`: CSV files used by the tests along with profiling output
90
+    used in chapter 8.
91
+
92
+  * `fuzz`: Example of using FuzzBert from chapter 6.
93
+
94
+  * `irb`: All of the scripts used to generate the IRB output in the
95
+    book, organized by chapter.
96
+
97
+  * `lib`: Code used in the book for both examples and also by the IRB
98
+    scripts.
99
+
100
+## Ruby Version
101
+
102
+Unless otherwise noted, all of the code in this repository works with
103
+the following Ruby versions:
104
+
105
+  * 1.9.3 (Official Ruby)
106
+  * 2.0.0 (Official Ruby)
107
+  * 2.1.0 (Official Ruby)
108
+  * 2.1.1 (Official Ruby)
109
+  * 2.1.2 (Official Ruby)
110
+
111
+Where:
112
+
113
+Official Ruby:
114
+  ~ The original and [official][ruby-home] implementation of the Ruby
115
+    interpreter and language by the Ruby Core team (MRI).
116
+
117
+Files that only work with a specific version of Ruby will have a
118
+version specifier in their name.  For example:
119
+
120
+~~~
121
+patching_2_1_test.rb
122
+~~~
123
+
124
+This file will only work with Ruby 2.1 or greater.  The `Rakefile`
125
+ensures that files are omitted from testing if the version of the Ruby
126
+interpreter isn't appropriate.
127
+
128
+## Contacting the Author
129
+
130
+If you want to contact the author about the source code in this
131
+repository please look at the *Issues and Pull Requests* section
132
+above.  For non-source code related topics, you can reach the author
133
+through his [website][peter j. jones].
134
+
135
+The author tweets Ruby tips at [@EffectiveRuby][] and personal junk at
136
+[@contextualdev][].
137
+
138
+[effective ruby]: http://www.effectiveruby.com/
139
+[peter j. jones]: http://www.devalot.com/
140
+[ruby-home]: http://www.ruby-lang.org/
141
+[rbenv]: https://github.com/sstephenson/rbenv
142
+[@effectiveruby]: https://twitter.com/EffectiveRuby
143
+[@contextualdev]: https://twitter.com/contextualdev

+ 25
- 0
Rakefile View File

@@ -0,0 +1,25 @@
1
+################################################################################
2
+# This file is part of the package effrb. It is subject to the license
3
+# terms in the LICENSE.md file found in the top-level directory of
4
+# this distribution and at https://github.com/pjones/effrb. No part of
5
+# the effrb package, including this file, may be copied, modified,
6
+# propagated, or distributed except according to the terms contained
7
+# in the LICENSE.md file.
8
+
9
+################################################################################
10
+require('rake/testtask')
11
+
12
+################################################################################
13
+task(:default => [:test])
14
+
15
+################################################################################
16
+Rake::TestTask.new do |t|
17
+  n,m,_ = RUBY_VERSION.split('.').map(&:to_i)
18
+
19
+  files = Dir.glob("*/*_test.rb")
20
+  files.reject! {|f| f.match(/2_0/)} unless n >= 2
21
+  files.reject! {|f| f.match(/2_1/)} unless n > 2 || (n == 2 && m >= 1)
22
+
23
+  t.test_files = files
24
+  t.warning    = true
25
+end

+ 225
- 0
assumptions/exceptions_test.rb View File

@@ -0,0 +1,225 @@
1
+################################################################################
2
+# This file is part of the package effrb. It is subject to the license
3
+# terms in the LICENSE.md file found in the top-level directory of
4
+# this distribution and at https://github.com/pjones/effrb. No part of
5
+# the effrb package, including this file, may be copied, modified,
6
+# propagated, or distributed except according to the terms contained
7
+# in the LICENSE.md file.
8
+
9
+################################################################################
10
+require(File.expand_path('../lib/test.rb', File.dirname(__FILE__)))
11
+
12
+################################################################################
13
+class RescueAssumptionTests < MiniTest::Unit::TestCase
14
+
15
+  ##############################################################################
16
+  class FakeError < StandardError; end
17
+
18
+  ##############################################################################
19
+  def uninitialized_vars
20
+    x = 1
21
+    raise("stop")
22
+    y = 2
23
+    z = 3
24
+  ensure
25
+    return [x, y, z]
26
+  end
27
+
28
+  ##############################################################################
29
+  def test_uninitialized_vars
30
+    vars = uninitialized_vars
31
+    assert_equal([1, nil, nil], vars)
32
+  end
33
+
34
+  ##############################################################################
35
+  def return_exception
36
+    raise("1")
37
+  ensure
38
+    return $!.to_s
39
+  end
40
+
41
+  ##############################################################################
42
+  def break_inside_ensure
43
+    %w(hello).each do |word|
44
+      begin
45
+        raise("WTF?")
46
+      ensure
47
+        break 2
48
+      end
49
+    end
50
+  end
51
+
52
+  ##############################################################################
53
+  def next_inside_ensure
54
+    [3].each do |word|
55
+      begin
56
+        raise("WTF?")
57
+      ensure
58
+        next
59
+      end
60
+    end
61
+  end
62
+
63
+  ##############################################################################
64
+  def nil_exception
65
+    return 4
66
+  ensure
67
+    return $!
68
+  end
69
+
70
+  ##############################################################################
71
+  def exception_type
72
+    raise(Exception.new("foo"))
73
+  ensure
74
+    assert_equal(Exception, $!.class)
75
+    return 1
76
+  end
77
+
78
+  ##############################################################################
79
+  def test_ensure_clause
80
+    assert_equal("1", return_exception)
81
+    assert_equal(2,   break_inside_ensure)
82
+    assert_equal([3], next_inside_ensure)
83
+    assert_equal(nil, nil_exception)
84
+    assert_equal(1, exception_type)
85
+  end
86
+
87
+  ##############################################################################
88
+  def return_in_rescue
89
+    raise("boom")
90
+  rescue
91
+    return 1
92
+  end
93
+
94
+  ##############################################################################
95
+  def ensure_raise
96
+    return 1
97
+  rescue
98
+    return 2
99
+  ensure
100
+    raise("boom")
101
+  end
102
+
103
+  ##############################################################################
104
+  def raise_because_no_method_error
105
+    nil.surely_i_dont_need_to_worry_about_this
106
+  ensure
107
+    return 1
108
+  end
109
+
110
+  ##############################################################################
111
+  def complicated_redo
112
+    count = 2
113
+
114
+    [1,2,3].each do |n|
115
+      count -= 1
116
+      begin
117
+        raise("boom") if count > 0
118
+      ensure
119
+        redo if count > 0
120
+      end
121
+    end
122
+
123
+    return 1
124
+  end
125
+
126
+  ##############################################################################
127
+  def complicated_flow
128
+    catch(:foo) do
129
+      [1, 2, 3].each do |n|
130
+        begin
131
+          raise("boom")
132
+        ensure
133
+          throw(:foo, 'hello')
134
+        end
135
+      end
136
+    end
137
+  end
138
+
139
+  ##############################################################################
140
+  def order_matters
141
+    raise(RuntimeError)
142
+  rescue StandardError
143
+    return 1
144
+  rescue RuntimeError
145
+    return 2
146
+  end
147
+
148
+  ##############################################################################
149
+  def return_runtime_error (x)
150
+    assert_equal(1, x)
151
+    RuntimeError
152
+  end
153
+
154
+  ##############################################################################
155
+  def expressions_work
156
+    x = 1
157
+    raise(RuntimeError)
158
+  rescue return_runtime_error(x)
159
+    return 1
160
+  rescue StandardError
161
+    return 2
162
+  end
163
+
164
+  ##############################################################################
165
+  def indirection_a
166
+    raise(FakeError)
167
+  rescue FakeError
168
+    return 1
169
+  end
170
+
171
+  ##############################################################################
172
+  def indirection_b
173
+    raise(RuntimeError)
174
+  rescue RuntimeError
175
+    return indirection_a
176
+  end
177
+
178
+  ##############################################################################
179
+  # Generates (SyntaxError)
180
+  # def retry_in_bad_place
181
+  #   begin
182
+  #     return 1
183
+  #   ensure
184
+  #     retry
185
+  #   end
186
+  # end
187
+
188
+  ##############################################################################
189
+  class Helper
190
+    attr_accessor(:ensure_called)
191
+  end
192
+
193
+  ##############################################################################
194
+  def ensure_and_throw
195
+    helper = Helper.new
196
+    throw(:jump, helper)
197
+  ensure
198
+    helper.ensure_called = true
199
+  end
200
+
201
+  ##############################################################################
202
+  def catch_helper
203
+    helper = catch(:jump) do
204
+      ensure_and_throw
205
+      nil
206
+    end
207
+
208
+    assert(helper)
209
+    assert(helper.ensure_called)
210
+    return 1
211
+  end
212
+
213
+  ##############################################################################
214
+  def test_assumptions
215
+    assert_equal(1, return_in_rescue)
216
+    assert_raises(RuntimeError) {ensure_raise}
217
+    assert_equal(1, raise_because_no_method_error)
218
+    assert_equal(1, complicated_redo)
219
+    assert_equal('hello', complicated_flow)
220
+    assert_equal(1, order_matters)
221
+    assert_equal(1, expressions_work)
222
+    assert_equal(1, indirection_b)
223
+    assert_equal(1, catch_helper)
224
+  end
225
+end

+ 140
- 0
assumptions/meta_test.rb View File

@@ -0,0 +1,140 @@
1
+################################################################################
2
+# This file is part of the package effrb. It is subject to the license
3
+# terms in the LICENSE.md file found in the top-level directory of
4
+# this distribution and at https://github.com/pjones/effrb. No part of
5
+# the effrb package, including this file, may be copied, modified,
6
+# propagated, or distributed except according to the terms contained
7
+# in the LICENSE.md file.
8
+
9
+################################################################################
10
+require(File.expand_path('../lib/test.rb', File.dirname(__FILE__)))
11
+require(File.expand_path('../lib/meta.rb', File.dirname(__FILE__)))
12
+
13
+################################################################################
14
+class MetaAssumptionTests < MiniTest::Unit::TestCase
15
+
16
+  ##############################################################################
17
+  class InstanceMethodWatcher
18
+    def self.calls
19
+      @@calls ||= Hash.new(0)
20
+    end
21
+
22
+    def self.method_added (m); calls[:added] += 1; end
23
+    def self.method_removed (m); calls[:removed] =+ 1; end
24
+    def self.method_undefined (m); calls[:undef] += 1; end
25
+
26
+    def hello; end
27
+    remove_method(:hello)
28
+
29
+    def hello; end
30
+    undef_method(:hello)
31
+  end
32
+
33
+  ##############################################################################
34
+  def test_instance_method_watcher
35
+    hash = InstanceMethodWatcher.calls
36
+    assert_equal(2, hash[:added])
37
+    assert_equal(1, hash[:removed])
38
+    assert_equal(1, hash[:undef])
39
+  end
40
+
41
+  ##############################################################################
42
+  class SingletonMethodWatcher
43
+    def self.calls
44
+      @@calls ||= Hash.new(0)
45
+    end
46
+
47
+    def self.singleton_method_added (m); calls[:added] += 1; end
48
+    def self.singleton_method_removed (m); calls[:removed] =+ 1; end
49
+    def self.singleton_method_undefined (m); calls[:undef] += 1; end
50
+
51
+    def self.hello; end
52
+    class << self; remove_method(:hello); end
53
+
54
+    def self.hello; end
55
+    class << self; undef_method(:hello); end
56
+  end
57
+
58
+  ##############################################################################
59
+  def test_singleton_method_watcher
60
+    hash = SingletonMethodWatcher.calls
61
+    assert_equal(5, hash[:added]) # watch out!
62
+    assert_equal(1, hash[:removed])
63
+    assert_equal(1, hash[:undef])
64
+  end
65
+
66
+  ##############################################################################
67
+  class MissingDelegator
68
+    def initialize; @hash = {}; end
69
+    def respond_to_missing? (m, *); @hash.respond_to?(m); end
70
+    def method_missing (m, *a); @hash.send(m, *a); end
71
+  end
72
+
73
+  ##############################################################################
74
+  def test_missing_delegator
75
+    d = MissingDelegator.new
76
+    assert(d.respond_to?(:assoc))
77
+    assert(!d.public_methods.include?(:assoc))
78
+  end
79
+
80
+  ##############################################################################
81
+  class HookClassParent
82
+    def self.hook_class
83
+      @hook_class ||= nil
84
+    end
85
+
86
+    def self.method_added (m)
87
+      @hook_class = self
88
+    end
89
+  end
90
+
91
+  ##############################################################################
92
+  class HookClassChild < HookClassParent
93
+    def foo; end
94
+  end
95
+
96
+  ##############################################################################
97
+  def test_hook_class
98
+    assert_equal(HookClassChild, HookClassChild.hook_class)
99
+  end
100
+
101
+  ##############################################################################
102
+  class DefineMethodWithBlock
103
+    define_method(:foo) {|*args, &block| block.call(*args)}
104
+  end
105
+
106
+  ##############################################################################
107
+  def test_define_method_with_block
108
+    obj = DefineMethodWithBlock.new
109
+    str = "h"
110
+    obj.foo(str) {|x| x.upcase!}
111
+    assert_equal("H", str)
112
+  end
113
+
114
+  ##############################################################################
115
+  def test_instance_eval_yields_self
116
+    widget = InstanceEvalWidget::Widget.new {|w| w.name = "foo"}
117
+    assert_equal("foo", widget.name)
118
+  end
119
+
120
+  ##############################################################################
121
+  module IncludeA
122
+    def foo
123
+    end
124
+  end
125
+
126
+  module IncludeB
127
+    include(IncludeA)
128
+  end
129
+
130
+  class IncludeC
131
+    include(IncludeB)
132
+    extend(IncludeB)
133
+  end
134
+
135
+  ##############################################################################
136
+  def test_methods_from_module_include
137
+    assert(IncludeC.public_instance_methods.include?(:foo))
138
+    assert(IncludeC.respond_to?(:foo))
139
+  end
140
+end

+ 265
- 0
assumptions/oop_test.rb View File

@@ -0,0 +1,265 @@
1
+################################################################################
2
+# This file is part of the package effrb. It is subject to the license
3
+# terms in the LICENSE.md file found in the top-level directory of
4
+# this distribution and at https://github.com/pjones/effrb. No part of
5
+# the effrb package, including this file, may be copied, modified,
6
+# propagated, or distributed except according to the terms contained
7
+# in the LICENSE.md file.
8
+
9
+################################################################################
10
+require(File.expand_path('../lib/test.rb', File.dirname(__FILE__)))
11
+
12
+################################################################################
13
+class OOPAssumptionTests < MiniTest::Unit::TestCase
14
+
15
+  ##############################################################################
16
+  module ModA; def method_a () return :mod_a end; end
17
+  module ModB; def method_b () return :mod_b end; end
18
+
19
+  ##############################################################################
20
+  class ClsA
21
+    # Bring in methods from a module before they are defined in the
22
+    # class.  Order doesn't matter, but the tests below confirm for
23
+    # all recent versions of Ruby.
24
+    include(ModA)
25
+
26
+    def method_a
27
+      return :cls_a
28
+    end
29
+
30
+    def method_b
31
+      return :cls_a
32
+    end
33
+
34
+    # Bring in methods from a module after they have already been
35
+    # defined in the class.  See note above.
36
+    include(ModB)
37
+  end
38
+
39
+  ##############################################################################
40
+  def test_class_before_module
41
+    # Test that modules are one level above a class in the hierarchy.
42
+    cls_a = ClsA.new
43
+    assert_equal(:cls_a, cls_a.method_a)
44
+    assert_equal(:cls_a, cls_a.method_b)
45
+
46
+    # Make sure the methods are actually reachable.
47
+    cls_b = Class.new
48
+    cls_b.send(:include, ModA)
49
+    assert_equal(:mod_a, cls_b.new.method_a)
50
+  end
51
+
52
+  ##############################################################################
53
+  class Parent
54
+    attr_accessor(:var)
55
+    def initialize (x=0) @var = x; end
56
+  end
57
+
58
+  ##############################################################################
59
+  class ChildA < Parent
60
+    def initialize (x) super; end
61
+  end
62
+
63
+  ##############################################################################
64
+  class ChildB < Parent
65
+    def initialize (x) super(x); end
66
+  end
67
+
68
+  ##############################################################################
69
+  class ChildC < Parent
70
+    def initialize (x) super(); end
71
+  end
72
+
73
+  ##############################################################################
74
+  def test_super_calls
75
+    assert_equal(1, ChildA.new(1).var)
76
+    assert_equal(1, ChildB.new(1).var)
77
+    assert_equal(0, ChildC.new(1).var)
78
+  end
79
+
80
+  ##############################################################################
81
+  class ChildD < Parent
82
+    # no initialize.
83
+  end
84
+
85
+  ##############################################################################
86
+  class SuperInMethodMissingError < StandardError; end
87
+
88
+  ##############################################################################
89
+  class ChildE < ChildD
90
+    attr_reader(:foo_var)
91
+    def initialize (*args); super; end
92
+
93
+    def foo
94
+      @foo_var = :in_foo
95
+      super
96
+    end
97
+
98
+    def method_missing (m)
99
+      case m
100
+      when :foo, :bar
101
+        raise(SuperInMethodMissingError)
102
+      else
103
+        super
104
+      end
105
+    end
106
+  end
107
+
108
+  ##############################################################################
109
+  class ChildF < ChildE
110
+    def bar
111
+      super
112
+    end
113
+  end
114
+
115
+  ##############################################################################
116
+  def test_super_searches_up_more_than_one_level
117
+    assert_equal(1, ChildE.new(1).var)
118
+  end
119
+
120
+  ##############################################################################
121
+  def test_super_to_method_missing
122
+    child_e = ChildE.new
123
+
124
+    assert_raises(SuperInMethodMissingError) {child_e.foo}
125
+    assert_equal(:in_foo, child_e.foo_var)
126
+
127
+    # method_missing from higher in the hierarchy works too.
128
+    assert_raises(SuperInMethodMissingError) {ChildF.new.bar}
129
+  end
130
+
131
+  ##############################################################################
132
+  class BaseA
133
+    def method_a () :base_a; end
134
+  end
135
+
136
+  ##############################################################################
137
+  class DerivedA
138
+    include(ModA)
139
+    def method_a () super; end
140
+  end
141
+
142
+  ##############################################################################
143
+  def test_super_goes_to_mod_first
144
+    assert_equal(:mod_a, DerivedA.new.method_a)
145
+  end
146
+
147
+  ##############################################################################
148
+  class BaseB
149
+    def method_1 (x=0, &block)
150
+      block.call(x)
151
+    end
152
+    def method_2 (x=0, &block)
153
+      block.call(x)
154
+    end
155
+    def method_3 (x=0, &block)
156
+      block.call(x)
157
+    end
158
+    def method_4 (x=0, &block)
159
+      block.call(x)
160
+    end
161
+  end
162
+
163
+  ##############################################################################
164
+  class DerivedB < BaseB
165
+    def method_1 (x, &block)
166
+      super
167
+    end
168
+    def method_2 (x, &block)
169
+      super(&block)
170
+    end
171
+    def method_3 (x, &block)
172
+      super(x, &block)
173
+    end
174
+    def method_4 (x, &block)
175
+      super {|y| block.call(y)}
176
+    end
177
+  end
178
+
179
+  ##############################################################################
180
+  def test_super_and_blocks
181
+    assert_equal(:foo, DerivedB.new.method_1(:foo) {|x| x})
182
+    assert_equal(0,    DerivedB.new.method_2(:bar) {|x| x})
183
+    assert_equal(:bar, DerivedB.new.method_3(:bar) {|x| x})
184
+    assert_equal(:bar, DerivedB.new.method_4(:bar) {|x| x})
185
+  end
186
+
187
+  ##############################################################################
188
+  class Setter
189
+    attr_reader(:foo)
190
+
191
+    def initialize (foo)
192
+      nowhitespace=(foo)
193
+    end
194
+
195
+    def nowhitespace= (foo)
196
+      @foo = foo
197
+    end
198
+  end
199
+
200
+  ##############################################################################
201
+  def test_setter_class
202
+    assert_equal(nil, Setter.new(1).foo)
203
+  end
204
+
205
+  ##############################################################################
206
+  module UnqualifiedA
207
+    module UnqualifiedB
208
+      FOO = 1
209
+
210
+      class Bar
211
+        def self.bar
212
+          UnqualifiedB::FOO
213
+        end
214
+      end
215
+    end
216
+  end
217
+
218
+  ##############################################################################
219
+  def test_partially_qualified_names
220
+    assert_equal(UnqualifiedA::UnqualifiedB::FOO,
221
+                 UnqualifiedA::UnqualifiedB::Bar.bar)
222
+  end
223
+
224
+  ##############################################################################
225
+  class Protected1
226
+    protected
227
+    def foo () 1; end
228
+  end
229
+
230
+  ##############################################################################
231
+  class Protected2 < Protected1
232
+    def go (other)
233
+      other.foo
234
+    end
235
+  end
236
+
237
+  ##############################################################################
238
+  class Protected3 < Protected2
239
+    def go2 (other)
240
+      other.foo
241
+    end
242
+  end
243
+
244
+  ##############################################################################
245
+  class Protected4 < Protected3
246
+    protected
247
+    def foo () 2; end
248
+  end
249
+
250
+  ##############################################################################
251
+  def test_protected
252
+    p2 = Protected2.new
253
+    p3 = Protected3.new
254
+    p4 = Protected4.new
255
+
256
+    assert_equal(1, p2.go(p3))
257
+    assert_equal(1, p3.go(p2))
258
+    assert_equal(1, p3.go2(p2))
259
+    assert_raises(NoMethodError) {p2.go(p4)}
260
+
261
+    assert(p3.kind_of?(p2.class))
262
+    assert(!p2.kind_of?(p3.class))
263
+
264
+  end
265
+end

+ 60
- 0
benchmarks/collections.rb View File

@@ -0,0 +1,60 @@
1
+#!/usr/bin/env ruby -w
2
+
3
+################################################################################
4
+# This file is part of the package effrb. It is subject to the license
5
+# terms in the LICENSE.md file found in the top-level directory of
6
+# this distribution and at https://github.com/pjones/effrb. No part of
7
+# the effrb package, including this file, may be copied, modified,
8
+# propagated, or distributed except according to the terms contained
9
+# in the LICENSE.md file.
10
+
11
+################################################################################
12
+require('benchmark')
13
+require('set')
14
+
15
+################################################################################
16
+module FastestCollection
17
+
18
+  ##############################################################################
19
+  ELEMENTS = 1_000_000
20
+  ITERATIONS = 100
21
+
22
+  ##############################################################################
23
+  def self.array
24
+    (1..ELEMENTS).to_a.map(&:to_s)
25
+  end
26
+
27
+  ##############################################################################
28
+  def self.hash
29
+    Hash[array.zip((1..ELEMENTS).to_a)]
30
+  end
31
+
32
+  ##############################################################################
33
+  def self.range
34
+    1..ELEMENTS
35
+  end
36
+
37
+  ##############################################################################
38
+  def self.set
39
+    Set.new(array)
40
+  end
41
+
42
+  ##############################################################################
43
+  def self.run
44
+    array_ = array
45
+    hash_  = hash
46
+    range_ = range
47
+    set_   = set
48
+    key    = (ELEMENTS - 1).to_s
49
+
50
+    Benchmark.bmbm do |bm|
51
+      bm.report(" array:") {ITERATIONS.times {array_.include?(key)}}
52
+      bm.report("  hash:") {ITERATIONS.times {hash_.include?(key)}}
53
+      bm.report(" range:") {ITERATIONS.times {range_.include?(ELEMENTS - 1)}}
54
+      bm.report("   set:") {ITERATIONS.times {set_.include?(key)}}
55
+    end
56
+  end
57
+end
58
+
59
+################################################################################
60
+FastestCollection.run

+ 44
- 0
benchmarks/literal.rb View File

@@ -0,0 +1,44 @@
1
+#!/usr/bin/env ruby -w
2
+
3
+################################################################################
4
+# This file is part of the package effrb. It is subject to the license
5
+# terms in the LICENSE.md file found in the top-level directory of
6
+# this distribution and at https://github.com/pjones/effrb. No part of
7
+# the effrb package, including this file, may be copied, modified,
8
+# propagated, or distributed except according to the terms contained
9
+# in the LICENSE.md file.
10
+
11
+################################################################################
12
+class Worker
13
+
14
+  ##############################################################################
15
+  LOOPS = 1_000
16
+
17
+  ##############################################################################
18
+  def initialize (name, &block)
19
+    stat_a = GC.stat
20
+    LOOPS.times(&block)
21
+    stat_b = GC.stat
22
+
23
+    before = stat_a[:total_allocated_object] || 0
24
+    after  = stat_b[:total_allocated_object] || 0
25
+
26
+    # The `times` method creates one object.
27
+    after -= 1 if after > 0
28
+
29
+    diff   = (after - before).to_s.rjust(6, ' ')
30
+    $stdout.puts(name.rjust(15, ' ') + ': ' + diff)
31
+  end
32
+end
33
+
34
+Worker.new("nothing")        {}
35
+Worker.new("int")            {1}
36
+Worker.new("[int]")          {[1, 2, 3]}
37
+Worker.new("[int].freeze")   {[1, 2, 3].freeze}
38
+Worker.new("[str]")          {%w(a b c)}
39
+Worker.new("[str].freeze")   {%w(a b c).freeze}
40
+Worker.new("{int}")          {{1 => 2}}
41
+Worker.new("{int}.freeze")   {{1 => 2}.freeze}
42
+Worker.new("string")         {"abc"}
43
+Worker.new("string.freeze")  {"abc".freeze}
44
+Worker.new("string.freeze2") {"abc".freeze}

+ 39
- 0
benchmarks/meta.rb View File

@@ -0,0 +1,39 @@
1
+#!/usr/bin/env ruby -w
2
+
3
+################################################################################
4
+# This file is part of the package effrb. It is subject to the license
5
+# terms in the LICENSE.md file found in the top-level directory of
6
+# this distribution and at https://github.com/pjones/effrb. No part of
7
+# the effrb package, including this file, may be copied, modified,
8
+# propagated, or distributed except according to the terms contained
9
+# in the LICENSE.md file.
10
+
11
+################################################################################
12
+require('benchmark')
13
+require('ostruct')
14
+
15
+################################################################################
16
+module MethodMissing
17
+
18
+  ##############################################################################
19
+  ITERATIONS = 1_000_000
20
+
21
+  ##############################################################################
22
+  class Silly
23
+    def one; 1; end
24
+    def method_missing (*); 2; end
25
+  end
26
+
27
+  ##############################################################################
28
+  def self.run
29
+    obj = Silly.new
30
+
31
+    Benchmark.bmbm do |bm|
32
+      bm.report(" one:") {ITERATIONS.times {obj.one}}
33
+      bm.report(" two:") {ITERATIONS.times {obj.two}}
34
+    end
35
+  end
36
+end
37
+
38
+################################################################################
39
+MethodMissing.run

+ 65
- 0
benchmarks/oop.rb View File

@@ -0,0 +1,65 @@
1
+#!/usr/bin/env ruby -w
2
+
3
+################################################################################
4
+# This file is part of the package effrb. It is subject to the license
5
+# terms in the LICENSE.md file found in the top-level directory of
6
+# this distribution and at https://github.com/pjones/effrb. No part of
7
+# the effrb package, including this file, may be copied, modified,
8
+# propagated, or distributed except according to the terms contained
9
+# in the LICENSE.md file.
10
+
11
+################################################################################
12
+require('benchmark')
13
+require('ostruct')
14
+
15
+################################################################################
16
+module StructOrHash
17
+
18
+  ##############################################################################
19
+  ITERATIONS = 1_000_000
20
+
21
+  ##############################################################################
22
+  St = Struct.new(:a, :b)
23
+
24
+  ##############################################################################
25
+  def self.struct
26
+    s = St.new(1, 2)
27
+
28
+    ITERATIONS.times do
29
+      s.a
30
+      s.b += 1
31
+    end
32
+  end
33
+
34
+  ##############################################################################
35
+  def self.ostruct
36
+    o = OpenStruct.new(:a => 1, :b  => 2)
37
+
38
+    ITERATIONS.times do
39
+      o.a
40
+      o.b += 1
41
+    end
42
+  end
43
+
44
+  ##############################################################################
45
+  def self.hash
46
+    h = Hash[:a, 1, :b, 2] # Try to level the playing field with St.
47
+
48
+    ITERATIONS.times do
49
+      h[:a]
50
+      h[:b] += 1
51
+    end
52
+  end
53
+
54
+  ##############################################################################
55
+  def self.run
56
+    Benchmark.bmbm do |bm|
57
+      bm.report(" struct:") {struct}
58
+      bm.report("ostruct:") {ostruct}
59
+      bm.report("   hash:") {hash}
60
+    end
61
+  end
62
+end
63
+
64
+################################################################################
65
+StructOrHash.run

+ 66
- 0
collections/array_test.rb View File

@@ -0,0 +1,66 @@
1
+################################################################################
2
+# This file is part of the package effrb. It is subject to the license
3
+# terms in the LICENSE.md file found in the top-level directory of
4
+# this distribution and at https://github.com/pjones/effrb. No part of
5
+# the effrb package, including this file, may be copied, modified,
6
+# propagated, or distributed except according to the terms contained
7
+# in the LICENSE.md file.
8
+
9
+################################################################################
10
+require(File.expand_path('../lib/test.rb', File.dirname(__FILE__)))
11
+
12
+################################################################################
13
+class ArrayTest < MiniTest::Unit::TestCase
14
+
15
+  ##############################################################################
16
+  module Assumed
17
+    # <<: assumed-array
18
+    class Pizza
19
+      def initialize (toppings)
20
+        toppings.each do |topping|
21
+          add_and_price_topping(topping)
22
+        end
23
+      end
24
+
25
+      # ...
26
+    end
27
+    # :>>
28
+
29
+    class Pizza
30
+      attr_reader(:topping_count)
31
+      def add_and_price_topping (x)
32
+        @topping_count ||= 0
33
+        @topping_count  += 1
34
+      end
35
+    end
36
+  end
37
+
38
+  module Forced
39
+    class Pizza < Assumed::Pizza; end
40
+
41
+    # <<: forced-array
42
+    class Pizza
43
+      def initialize (toppings)
44
+        Array(toppings).each do |topping|
45
+          add_and_price_topping(topping)
46
+        end
47
+      end
48
+
49
+      # ...
50
+    end
51
+    # :>>
52
+  end
53
+
54
+  ##############################################################################
55
+  def test_assumed
56
+    assert_equal(1, Assumed::Pizza.new(["foo"]).topping_count)
57
+    assert_raises(NoMethodError) {Assumed::Pizza.new(nil)}
58
+  end
59
+
60
+  ##############################################################################
61
+  def test_forced
62
+    assert_equal(1,   Forced::Pizza.new(["foo"]).topping_count)
63
+    assert_equal(1,   Forced::Pizza.new("foo").topping_count)
64
+    assert_equal(nil, Forced::Pizza.new(nil).topping_count)
65
+  end
66
+end

+ 97
- 0
collections/delegation_test.rb View File

@@ -0,0 +1,97 @@
1
+################################################################################
2
+# This file is part of the package effrb. It is subject to the license
3
+# terms in the LICENSE.md file found in the top-level directory of
4
+# this distribution and at https://github.com/pjones/effrb. No part of
5
+# the effrb package, including this file, may be copied, modified,
6
+# propagated, or distributed except according to the terms contained
7
+# in the LICENSE.md file.
8
+
9
+################################################################################
10
+require(File.expand_path('../lib/test.rb', File.dirname(__FILE__)))
11
+
12
+################################################################################
13
+class DelegationTest < MiniTest::Unit::TestCase
14
+
15
+  ##############################################################################
16
+  # <<: raising-hash
17
+  require('forwardable')
18
+
19
+  class RaisingHash
20
+    extend(Forwardable)
21
+    include(Enumerable)
22
+
23
+    def_delegators(:@hash, :[], :[]=, :delete, :each,
24
+                           :keys, :values, :length,
25
+                           :empty?, :has_key?)
26
+  end
27
+  # :>>
28
+
29
+  ##############################################################################
30
+  class RaisingHash
31
+    # <<: erase
32
+    # Forward self.erase! to @hash.delete
33
+    def_delegator(:@hash, :delete, :erase!)
34
+    # :>>
35
+  end
36
+
37
+  ##############################################################################
38
+  class RaisingHash
39
+    # <<: initialize
40
+    def initialize
41
+      @hash = Hash.new do |hash, key|
42
+        raise(KeyError, "invalid key `#{key}'!")
43
+      end
44
+    end
45
+    # :>>
46
+
47
+    # <<: initialize-copy
48
+    def initialize_copy (other)
49
+      @hash = @hash.dup
50
+    end
51
+    # :>>
52
+
53
+    # <<: freeze
54
+    def freeze
55
+      @hash.freeze
56
+      super
57
+    end
58
+    # :>>
59
+
60
+    # <<: invert
61
+    def invert
62
+      other = self.class.new
63
+      other.replace!(@hash.invert)
64
+      other
65
+    end
66
+
67
+    protected
68
+
69
+    def replace! (hash)
70
+      hash.default_proc = @hash.default_proc
71
+      @hash = hash
72
+    end
73
+    # :>>
74
+  end
75
+
76
+  ##############################################################################
77
+  def test_raising_hash
78
+    h = RaisingHash.new
79
+    h[1] = 2
80
+
81
+    assert_equal(2, h[1])
82
+    assert_equal(1, h.length)
83
+    assert_equal([1], h.keys)
84
+    assert_equal([2], h.values)
85
+    assert_raises(KeyError) {h[3]}
86
+    assert_raises(KeyError) {h.dup[3]}
87
+    assert_raises(KeyError) {h.invert[3]}
88
+
89
+    invoked = h.reduce(false) do |bool, (k,v)|
90
+      assert_equal(1, k)
91
+      assert_equal(2, v)
92
+      true
93
+    end
94
+
95
+    assert(invoked)
96
+  end
97
+end

+ 39
- 0
collections/dup_test.rb View File

@@ -0,0 +1,39 @@
1
+################################################################################
2
+# This file is part of the package effrb. It is subject to the license
3
+# terms in the LICENSE.md file found in the top-level directory of
4
+# this distribution and at https://github.com/pjones/effrb. No part of
5
+# the effrb package, including this file, may be copied, modified,
6
+# propagated, or distributed except according to the terms contained
7
+# in the LICENSE.md file.
8
+
9
+################################################################################
10
+require(File.expand_path('../lib/test.rb', File.dirname(__FILE__)))
11
+require(File.expand_path('../lib/collections.rb', File.dirname(__FILE__)))
12
+
13
+################################################################################
14
+class DupTest < MiniTest::Unit::TestCase
15
+
16
+  ##############################################################################
17
+  def test_phone_number
18
+    number = "1-800-CALL-MEEE"
19
+    pn = MutatePhone::PhoneNumber.new(number)
20
+    assert_equal('1800', number)
21
+  end
22
+
23
+  ##############################################################################
24
+  def test_tuners
25
+    presets1 = %w(90.1 106.2 88.5)
26
+    presets2 = presets1.dup
27
+
28
+    t1 = MutateTuner::Tuner.new(presets1)
29
+    assert_equal(%w(90.1 88.5), presets1) # mutated
30
+
31
+    t2 = PersistentTuner::Tuner.new(presets2)
32
+    assert_equal(%w(90.1 106.2 88.5), presets2)
33
+    assert_equal(%w(90.1 88.5), t2.presets)
34
+
35
+    t3 = DupTuner::Tuner.new(presets2)
36
+    assert_equal(%w(90.1 106.2 88.5), presets2)
37
+    assert_equal(%w(90.1 88.5), t3.presets)
38
+  end
39
+end

+ 89
- 0
collections/hashdef_test.rb View File

@@ -0,0 +1,89 @@
1
+################################################################################
2
+# This file is part of the package effrb. It is subject to the license
3
+# terms in the LICENSE.md file found in the top-level directory of
4
+# this distribution and at https://github.com/pjones/effrb. No part of
5
+# the effrb package, including this file, may be copied, modified,
6
+# propagated, or distributed except according to the terms contained
7
+# in the LICENSE.md file.
8
+
9
+################################################################################
10
+require(File.expand_path('../lib/test.rb', File.dirname(__FILE__)))
11
+
12
+################################################################################
13
+class HashdefTest < MiniTest::Unit::TestCase
14
+
15
+  ##############################################################################
16
+  module WithOrEq
17
+    # <<: frequency
18
+    def frequency (array)
19
+      array.reduce({}) do |hash, element|
20
+        hash[element] ||= 0 # Make sure the key exists.
21
+        hash[element] += 1  # Then increment it.
22
+        hash                # Return the hash to reduce.
23
+      end
24
+    end
25
+    # :>>
26
+
27
+    module_function(:frequency)
28
+  end
29
+
30
+  ##############################################################################
31
+  module WithDefault
32
+    # <<: frequency-def
33
+    def frequency (array)
34
+      array.reduce(Hash.new(0)) do |hash, element|
35
+        hash[element] += 1 # Increment the value.
36
+        hash               # Return the hash to reduce.
37
+      end
38
+    end
39
+    # :>>
40
+
41
+    module_function(:frequency)
42
+  end
43
+
44
+  ##############################################################################
45
+  def test_frequency
46
+    array = [1, 2, 1, 3]
47
+    hash  = {1 => 2, 2 => 1, 3 => 1}
48
+
49
+    assert_equal(hash, WithOrEq.frequency(array))
50
+    assert_equal(hash, WithDefault.frequency(array))
51
+  end
52
+
53
+  ##############################################################################
54
+  module Funcs
55
+    def self.short_eq
56
+      hash = Hash.new(0)
57
+      key = 1
58
+
59
+      # <<: expand-eq
60
+      # Short version:
61
+      hash[key] += 1
62
+
63
+      # Expands to:
64
+      hash[key] = hash[key] + 1
65
+      # :>>
66
+
67
+      hash
68
+    end
69
+
70
+    def self.bad_check
71
+      hash = {}
72
+      key = 1
73
+
74
+      # <<: bad-check
75
+      if hash[key]
76
+        # ...
77
+      end
78
+      # :>>
79
+
80
+      hash
81
+    end
82
+  end
83
+
84
+  ##############################################################################
85
+  def test_funcs
86
+    assert_equal({1=>2}, Funcs.short_eq)
87
+    assert_equal({}, Funcs.bad_check)
88
+  end
89
+end

+ 141
- 0
collections/reduce_test.rb View File

@@ -0,0 +1,141 @@
1
+################################################################################
2
+# This file is part of the package effrb. It is subject to the license
3
+# terms in the LICENSE.md file found in the top-level directory of
4
+# this distribution and at https://github.com/pjones/effrb. No part of
5
+# the effrb package, including this file, may be copied, modified,
6
+# propagated, or distributed except according to the terms contained
7
+# in the LICENSE.md file.
8
+
9
+################################################################################
10
+require(File.expand_path('../lib/test.rb', File.dirname(__FILE__)))
11
+
12
+################################################################################
13
+class ReduceTest < MiniTest::Unit::TestCase
14
+
15
+  ##############################################################################
16
+  module LongSum
17
+    # <<: long-sum
18
+    def sum (enum)
19
+      enum.reduce(0) do |accumulator, element|
20
+        accumulator + element
21
+      end
22
+    end
23
+    # :>>
24
+
25
+    module_function(:sum)
26
+  end
27
+
28
+  ##############################################################################
29
+  module SumDefault
30
+    # <<: sum-default
31
+    def sum (enum)
32
+      enum.reduce do |accumulator, element|
33
+        accumulator + element
34
+      end
35
+    end
36
+    # :>>
37
+
38
+    module_function(:sum)
39
+  end
40
+
41
+  ##############################################################################
42
+  module ShortSum
43
+    # <<: short-sum
44
+    def sum (enum)
45
+      enum.reduce(0, :+)
46
+    end
47
+    # :>>
48
+
49
+    module_function(:sum)
50
+  end
51
+
52
+  ##############################################################################
53
+  def test_sum
54
+    enum = [1, 2, 3, 4, 5]
55
+    assert_equal(15, LongSum.sum(enum))
56
+    assert_equal(15, SumDefault.sum(enum))
57
+    assert_equal(15, ShortSum.sum(enum))
58
+  end
59
+
60
+  ##############################################################################
61
+  module Transform
62
+
63
+    def self.array_to_hash_with_map (array)
64
+      # <<: array-map
65
+      Hash[array.map {|x| [x, true]}]
66
+      # :>>
67
+    end
68
+
69
+    def self.array_to_hash_with_reduce (array)
70
+      # <<: array-reduce
71
+      array.reduce({}) do |hash, element|
72
+        hash.update(element => true)
73
+      end
74
+      # :>>
75
+    end
76
+  end
77
+
78
+  ##############################################################################
79
+  def test_transform
80
+    array = [1, 2, 3]
81
+    hash  = {1 => true, 2 => true, 3 => true}
82
+
83
+    assert_equal(hash, Transform.array_to_hash_with_map(array))
84
+    assert_equal(hash, Transform.array_to_hash_with_reduce(array))
85
+  end
86
+
87
+  ##############################################################################
88
+  module SelectMap
89
+    User = Struct.new(:age, :name)
90
+
91
+    def self.select_and_map (users)
92
+      # <<: user-select
93
+      users.select {|u| u.age >= 21}.map(&:name)
94
+      # :>>
95
+    end
96
+
97
+    def self.with_reduce (users)
98
+      # <<: user-reduce
99
+      users.reduce([]) do |names, user|
100
+        names << user.name if user.age >= 21
101
+        names
102
+      end
103
+      # :>>
104
+    end
105
+  end
106
+
107
+  ##############################################################################
108
+  def test_select_map
109
+    users = [
110
+      SelectMap::User.new(15, "Jordan"),
111
+      SelectMap::User.new(88, "Orin"),
112
+      SelectMap::User.new(15, "Kyle"),
113
+      SelectMap::User.new(22, "Frank"),
114
+    ]
115
+
116
+    assert_equal(%w(Orin Frank), SelectMap.select_and_map(users))
117
+    assert_equal(%w(Orin Frank), SelectMap.with_reduce(users))
118
+  end
119
+
120
+  ##############################################################################
121
+  module BadFold
122
+    def self.convert (array)
123
+      # <<: bad-fold
124
+      hash = {}
125
+
126
+      array.each do |element|
127
+        hash[element] = true
128
+      end
129
+      # :>>
130
+
131
+      hash
132
+    end
133
+  end
134
+
135
+  ##############################################################################
136
+  def test_bad_fold
137
+    array = [1, 2, 3]
138
+    hash  = {1 => true, 2 => true, 3 => true}
139
+    assert_equal(hash, BadFold.convert(array))
140
+  end
141
+end

+ 120
- 0
collections/set_test.rb View File

@@ -0,0 +1,120 @@
1
+################################################################################
2
+# This file is part of the package effrb. It is subject to the license
3
+# terms in the LICENSE.md file found in the top-level directory of
4
+# this distribution and at https://github.com/pjones/effrb. No part of
5
+# the effrb package, including this file, may be copied, modified,
6
+# propagated, or distributed except according to the terms contained
7
+# in the LICENSE.md file.
8
+
9
+################################################################################
10
+require(File.expand_path('../lib/test.rb', File.dirname(__FILE__)))
11
+
12
+################################################################################
13
+class SetTest < MiniTest::Unit::TestCase
14
+
15
+  ##############################################################################
16
+  DATA_FILE = File.expand_path('../data/weather-dups.csv', File.dirname(__FILE__))
17
+
18
+  ##############################################################################
19
+  module UsingArray
20
+    # <<: array
21
+    class Role
22
+      def initialize (name, permissions)
23
+        @name, @permissions = name, permissions
24
+      end
25
+
26
+      def can? (permission)
27
+        @permissions.include?(permission)
28
+      end
29
+    end
30
+    # :>>
31
+  end
32
+
33
+  ##############################################################################
34
+  module UsingHash
35
+    # <<: hash
36
+    class Role
37
+      def initialize (name, permissions)
38
+        @name = name
39
+        @permissions = Hash[permissions.map {|p| [p, true]}]
40
+      end
41
+
42
+      def can? (permission)
43
+        @permissions.include?(permission)
44
+      end
45
+    end
46
+    # :>>
47
+  end
48
+
49
+  ##############################################################################
50
+  module UsingSet
51
+    # <<: set
52
+    require('set')
53
+
54
+    class Role
55
+      def initialize (name, permissions)
56
+        @name, @permissions = name, Set.new(permissions)
57
+      end
58
+
59
+      def can? (permission)
60
+        @permissions.include?(permission)
61
+      end
62
+    end
63
+    # :>>
64
+
65
+    # <<: weather
66
+    require('set')
67
+    require('csv')
68
+
69
+    class AnnualWeather
70
+      Reading = Struct.new(:date, :high, :low) do
71
+        def eql? (other) date.eql?(other.date); end
72
+        def hash; date.hash; end
73
+      end
74
+
75
+      def initialize (file_name)
76
+        @readings = Set.new
77
+
78
+        CSV.foreach(file_name, headers: true) do |row|
79
+          @readings << Reading.new(Date.parse(row[2]),
80
+                                   row[10].to_f,
81
+                                   row[11].to_f)
82
+        end
83
+      end
84
+    end
85
+    # :>>
86
+
87
+    class AnnualWeather
88
+      attr_reader(:readings)
89
+      def mean
90
+        return 0.0 if @readings.size.zero?
91
+
92
+        total = @readings.reduce(0.0) do |sum, reading|
93
+          sum + (reading.high + reading.low) / 2.0
94
+        end
95
+
96
+        total / @readings.size.to_f
97
+      end
98
+
99
+    end
100
+  end
101
+
102
+  ##############################################################################
103
+  def test_role
104
+    [ UsingArray::Role,
105
+      UsingHash::Role,
106
+      UsingSet::Role,
107
+    ].each do |klass|
108
+      role = klass.new(klass.to_s, [:one, :two, :three])
109
+      assert(role.can?(:two))
110
+      assert(!role.can?(:four))
111
+    end
112
+  end
113
+
114
+  ##############################################################################
115
+  def test_weather
116
+    w = UsingSet::AnnualWeather.new(DATA_FILE)
117
+    assert_equal(10, w.readings.size)
118
+    assert_equal(53.4, w.mean)
119
+  end
120
+end

+ 2
- 0
coverage/Gemfile View File

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

+ 14
- 0
coverage/Gemfile.lock View File

@@ -0,0 +1,14 @@
1
+GEM
2
+  remote: https://rubygems.org/
3
+  specs:
4
+    multi_json (1.9.2)
5
+    simplecov (0.7.1)
6
+      multi_json (~> 1.0)
7
+      simplecov-html (~> 0.7.1)
8
+    simplecov-html (0.7.1)
9
+
10
+PLATFORMS
11
+  ruby
12
+
13
+DEPENDENCIES
14
+  simplecov (~> 0.7.1)

+ 14
- 0
coverage/Rakefile View File

@@ -0,0 +1,14 @@
1
+################################################################################
2
+# This file is part of the package effrb. It is subject to the license
3
+# terms in the LICENSE.md file found in the top-level directory of
4
+# this distribution and at https://github.com/pjones/effrb. No part of
5
+# the effrb package, including this file, may be copied, modified,
6
+# propagated, or distributed except according to the terms contained
7
+# in the LICENSE.md file.
8
+
9
+require('rake/testtask')
10
+
11
+Rake::TestTask.new do |t|
12
+  t.test_files = FileList['test.rb']
13
+  t.warning = true # Turn on Ruby warnings.
14
+end

+ 25
- 0
coverage/test.rb View File

@@ -0,0 +1,25 @@
1
+################################################################################
2
+# This file is part of the package effrb. It is subject to the license
3
+# terms in the LICENSE.md file found in the top-level directory of
4
+# this distribution and at https://github.com/pjones/effrb. No part of
5
+# the effrb package, including this file, may be copied, modified,
6
+# propagated, or distributed except according to the terms contained
7
+# in the LICENSE.md file.
8
+
9
+################################################################################
10
+require('simplecov')
11
+SimpleCov.start
12
+
13
+################################################################################
14
+require('minitest/autorun')
15
+require('./widget.rb')
16
+
17
+################################################################################
18
+class WidgetTest < MiniTest::Unit::TestCase
19
+
20
+  ##############################################################################
21
+  def test_can_set_name
22
+    w = Widget.new("Foo")
23
+    assert_equal("Foo", w.name)
24
+  end
25
+end

+ 26
- 0
coverage/widget.rb View File

@@ -0,0 +1,26 @@
1
+################################################################################
2
+# This file is part of the package effrb. It is subject to the license
3
+# terms in the LICENSE.md file found in the top-level directory of
4
+# this distribution and at https://github.com/pjones/effrb. No part of
5
+# the effrb package, including this file, may be copied, modified,
6
+# propagated, or distributed except according to the terms contained
7
+# in the LICENSE.md file.
8
+
9
+################################################################################
10
+class Widget
11
+
12
+  attr_accessor(:name, :quantity)
13
+
14
+  def initialize (name)
15
+    @name     = name
16
+    @quantity = 0
17
+  end
18
+
19
+  def inc
20
+    @quantity += 1
21
+  end
22
+
23
+  def dec
24
+    @quantity -= 1 if @quantity > 0
25
+  end
26
+end

+ 10
- 0
data/README.md View File

@@ -0,0 +1,10 @@
1
+# Test Data for Effective Ruby Examples
2
+
3
+## Weather Data
4
+
5
+The weather data was generated by the National Climatic Data Center
6
+([NCDC][]), a division of the National Oceanic and Atmospheric
7
+Administration ([NOAA][]).
8
+
9
+[NCDC]: http://www.ncdc.noaa.gov/
10
+[NOAA]: http://www.noaa.gov/

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

@@ -0,0 +1,15 @@
1
+STATION,STATION_NAME,DATE,EMXP,MXSD,DSNW,TPCP,TSNW,EMXT,EMNT,MMXT,MMNT,MNTM
2
+COOP:050848,BOULDER CO US,20130101,15,3,1,27,37,65,-4,46.2,19.7,33.0
3
+COOP:050848,BOULDER CO US,20130101,15,3,1,27,37,65,-4,46.2,19.7,33.0
4
+COOP:050848,BOULDER CO US,20130101,15,3,1,27,37,65,-4,46.2,19.7,33.0
5
+COOP:050848,BOULDER CO US,20130201,41,10,3,112,185,61,8,44.6,19.6,32.1
6
+COOP:050848,BOULDER CO US,20130301,82,9,4,172,228,76,3,54.0,27.0,40.5
7
+COOP:050848,BOULDER CO US,20130401,71,11,8,413,476,77,2,57.7,29.8,43.7
8
+COOP:050848,BOULDER CO US,20130501,132,9,2,266,123,87,17,71.7,43.6,57.6
9
+COOP:050848,BOULDER CO US,20130501,132,9,2,266,123,87,17,71.7,43.6,57.6
10
+COOP:050848,BOULDER CO US,20130501,132,9,2,266,123,87,17,71.7,43.6,57.6
11
+COOP:050848,BOULDER CO US,20130601,37,0,0,61,0,98,40,86.2,53.5,69.8
12
+COOP:050848,BOULDER CO US,20130701,23,0,0,103,0,98,51,86.8,57.5,72.2
13
+COOP:050848,BOULDER CO US,20130801,27,0,0,140,0,97,50,87.6,56.7,72.2
14
+COOP:050848,BOULDER CO US,20130901,908,0,0,1816,0,95,33,77.7,52.5,65.1
15
+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 @@
1
+STATION,STATION_NAME,DATE,EMXP,MXSD,DSNW,TPCP,TSNW,EMXT,EMNT,MMXT,MMNT,MNTM
2
+COOP:050848,BOULDER CO US,20130101,15,3,1,27,37,65,-4,46.2,19.7,33.0
3
+COOP:050848,BOULDER CO US,20130201,41,10,3,112,185,61,8,44.6,19.6,32.1
4
+COOP:050848,BOULDER CO US,20130301,82,9,4,172,228,76,3,54.0,27.0,40.5
5
+COOP:050848,BOULDER CO US,20130401,71,11,8,413,476,77,2,57.7,29.8,43.7
6
+COOP:050848,BOULDER CO US,20130501,132,9,2,266,123,87,17,71.7,43.6,57.6
7
+COOP:050848,BOULDER CO US,20130601,37,0,0,61,0,98,40,86.2,53.5,69.8
8
+COOP:050848,BOULDER CO US,20130701,23,0,0,103,0,98,51,86.8,57.5,72.2
9
+COOP:050848,BOULDER CO US,20130801,27,0,0,140,0,97,50,87.6,56.7,72.2
10
+COOP:050848,BOULDER CO US,20130901,908,0,0,1816,0,95,33,77.7,52.5,65.1
11
+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 @@
1
+Total allocated 892553
2
+Total retained 4680
3
+
4
+allocated memory by gem
5
+-----------------------------------
6
+2.1.1/lib x 75201418
7
+rubygems x 1181583
8
+other x 120
9
+
10
+allocated memory by location
11
+-----------------------------------
12
+lib/ruby/2.1.0/net/protocol.rb:153 x 35241433
13
+lib/ruby/2.1.0/csv.rb:1806 x 16217960
14
+lib/ruby/2.1.0/csv.rb:1833 x 16217960
15
+lib/ruby/2.1.0/csv.rb:1783 x 2918998
16
+
17
+retained memory by location
18
+-----------------------------------
19
+lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55 x 116966
20
+lib/ruby/2.1.0/uri/common.rb:853 x 20440
21
+lib/ruby/2.1.0/x86_64-linux/socket.so:0 x 17676
22
+lib/ruby/2.1.0/uri/common.rb:863 x 11680

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

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

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

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

+ 75
- 0
exceptions/ensure_return_test.rb View File

@@ -0,0 +1,75 @@
1
+################################################################################
2
+# This file is part of the package effrb. It is subject to the license
3
+# terms in the LICENSE.md file found in the top-level directory of
4
+# this distribution and at https://github.com/pjones/effrb. No part of
5
+# the effrb package, including this file, may be copied, modified,
6
+# propagated, or distributed except according to the terms contained
7
+# in the LICENSE.md file.
8
+
9
+################################################################################
10
+require(File.expand_path('../lib/test.rb', File.dirname(__FILE__)))
11
+
12
+################################################################################
13
+class EnsureTrickTest < MiniTest::Unit::TestCase
14
+
15
+  ##############################################################################
16
+  class SpecificError < StandardError; end
17
+  class TooStrongError < StandardError; end
18
+
19
+  ##############################################################################
20
+  # <<: return
21
+  def tricky
22
+    # ...
23
+    return 'horses'
24
+  ensure
25
+    return 'ponies'
26
+  end
27
+  # :>>
28
+
29
+  ##############################################################################
30
+  # <<: explicit
31
+  def explicit
32
+    # ...
33
+    return 'horses'
34
+  rescue SpecificError => e
35
+    # Recover from the exception.
36
+    return 'ponies'
37
+  ensure
38
+    # Clean up only.
39
+  end
40
+  # :>>
41
+
42
+  ##############################################################################
43
+  # <<: bare
44
+  def hammer
45
+    # ...
46
+    return 'hit'
47
+  rescue
48
+    # Discard exceptions.
49
+    return 'smash'
50
+  end
51
+  # :>>
52
+
53
+  ##############################################################################
54
+  def obscure
55
+    items = %w(jasmine lilac lavender)
56
+
57
+    # <<: next
58
+    items.each do |item|
59
+      begin
60
+        raise TooStrongError if item == 'lilac'
61
+      ensure
62
+        next # Cancels exception, continues iteration.
63
+      end
64
+    end
65
+    # :>>
66
+  end
67
+
68
+  ##############################################################################
69
+  def test_ensure
70
+    assert_equal('ponies', tricky)
71
+    assert_equal('horses', explicit)
72
+    assert_equal(%w(jasmine lilac lavender), obscure)
73
+    assert_equal('hit', hammer)
74
+  end
75
+end

+ 206
- 0
exceptions/resources_test.rb View File

@@ -0,0 +1,206 @@
1
+################################################################################
2
+# This file is part of the package effrb. It is subject to the license
3
+# terms in the LICENSE.md file found in the top-level directory of
4
+# this distribution and at https://github.com/pjones/effrb. No part of
5
+# the effrb package, including this file, may be copied, modified,
6
+# propagated, or distributed except according to the terms contained
7
+# in the LICENSE.md file.
8
+
9
+################################################################################
10
+require(File.expand_path('../lib/test.rb', File.dirname(__FILE__)))
11
+
12
+################################################################################
13
+class ResourcesTest < MiniTest::Unit::TestCase
14
+
15
+  ##############################################################################
16
+  class FileNaive
17
+    def initialize (file_name)
18
+      # <<: naive
19
+      file = File.open(file_name, 'w')
20
+      # ...
21
+      file.close
22
+      # :>>
23
+    end
24
+  end
25
+
26
+  ##############################################################################
27
+  def test_file_naive_is_created
28
+    file_name = 'some_file.txt'
29
+    assert(!File.exist?(file_name))
30
+    FileNaive.new(file_name)
31
+    assert(File.exist?(file_name))
32
+    File.unlink(file_name)
33
+  end
34
+
35
+  ##############################################################################
36
+  class FileBegin
37
+    def initialize (file_name)
38
+      # <<: begin
39
+      begin
40
+        file = File.open(file_name, 'w')
41
+        # ...
42
+      ensure
43
+        file.close if file
44
+      end
45
+      # :>>
46
+    end
47
+  end
48
+
49
+  ##############################################################################
50
+  # This is starting to look familiar.
51
+  def test_file_begin_is_created
52
+    file_name = 'some_file.txt'
53
+    assert(!File.exist?(file_name))
54
+    FileBegin.new(file_name)
55
+    assert(File.exist?(file_name))
56
+    File.unlink(file_name)
57
+  end
58
+
59
+  ##############################################################################
60
+  class FileBlock
61
+    def initialize (file_name)
62
+      # <<: block
63
+      File.open(file_name, 'w') do |file|
64
+        # ...
65
+      end
66
+      # :>>
67
+    end
68
+  end
69
+
70
+  ##############################################################################
71
+  # This is starting to look familiar.
72
+  def test_file_block_is_created
73
+    file_name = 'some_file.txt'
74
+    assert(!File.exist?(file_name))
75
+    FileBlock.new(file_name)
76
+    assert(File.exist?(file_name))
77
+    File.unlink(file_name)
78
+  end
79
+
80
+  ##############################################################################
81
+  # <<: class
82
+  class Lock
83
+    def self.acquire
84
+      lock = new  # Initialize the resource
85
+      lock.exclusive_lock!
86
+      yield(lock) # Give it to the block
87
+    ensure
88
+      # Make sure it gets unlocked.
89
+      lock.unlock if lock
90
+    end
91
+  end
92
+  # :>>
93
+
94
+  ##############################################################################
95
+  # Add a few more things.
96
+  class Lock
97
+    attr_reader(:unlocked)
98
+    def unlock () @unlocked = true; end
99
+    def exclusive_lock! () end
100
+  end
101
+
102
+  ##############################################################################
103
+  # <<: use
104
+  Lock.acquire do |lock|
105
+    # Raising an exception here is okay.
106
+  end
107
+  # :>>
108
+
109
+  ##############################################################################
110
+  def test_yield_unlock_happy_path
111
+    yielded = false
112
+
113
+    resource = Lock.acquire do |r|
114
+      yielded = true
115
+      r
116
+    end
117
+
118
+    assert(yielded, "should have yielded")
119
+    assert(resource.unlocked, "should have unlocked")
120
+  end
121
+
122
+  ##############################################################################
123
+  def test_yield_unlock_exception
124
+    checked = false
125
+    resource = nil
126
+
127
+    begin
128
+      Lock.acquire do |r|
129
+        resource = r
130
+        raise("blow up!")
131
+      end
132
+    rescue
133
+      checked = true
134
+      assert(!resource.nil?)
135
+      assert(resource.unlocked)
136
+    end
137
+
138
+    assert(checked)
139
+  end
140
+
141
+  ##############################################################################
142
+  module OptionalYield
143
+
144
+    ##############################################################################
145
+    # <<: class
146
+    class Lock
147
+      def self.acquire
148
+        lock = new # Initialize the resource.
149
+        lock.exclusive_lock!
150
+
151
+        if block_given?
152
+          yield(lock)
153
+        else
154
+          lock # Act more like Lock.new.
155
+        end
156
+      ensure
157
+        if block_given?
158
+          # Make sure it gets unlocked.
159
+          lock.unlock if lock
160
+        end
161
+      end
162
+    end
163
+    # :>>
164
+
165
+    ##############################################################################
166
+    # Add a few more things.
167
+    class Lock
168
+      attr_reader(:unlocked)
169
+      def unlock () @unlocked = true; end
170
+      def exclusive_lock! () end
171
+    end
172
+
173
+    ##############################################################################
174
+    def self.use_lock_with_block
175
+      # <<: use
176
+      Lock.acquire do |resource|
177
+        # Raising an exception here is okay.
178
+      end
179
+      # :>>
180
+    end
181
+
182
+    ############################################################################
183
+    def self.use_lock_without_block
184
+      # <<: new
185
+      lock = Lock.acquire
186
+      # Won't automatically unlock.
187
+      # :>>
188
+    end
189
+  end
190
+
191
+  ##############################################################################
192
+  def test_returns_block_value
193
+    yielded = OptionalYield::Lock.acquire {|r| 21}
194
+    assert_equal(21, yielded)
195
+  end
196
+
197
+  ##############################################################################
198
+  def test_acts_like_new
199
+    lock = OptionalYield.use_lock_without_block
200
+    assert(lock)
201
+    assert(lock.unlocked.nil?)
202
+
203
+    lock.unlock
204
+    assert(lock.unlocked)
205
+  end
206
+end

+ 117
- 0
exceptions/retry_test.rb View File

@@ -0,0 +1,117 @@
1
+################################################################################
2
+# This file is part of the package effrb. It is subject to the license
3
+# terms in the LICENSE.md file found in the top-level directory of
4
+# this distribution and at https://github.com/pjones/effrb. No part of
5
+# the effrb package, including this file, may be copied, modified,
6
+# propagated, or distributed except according to the terms contained
7
+# in the LICENSE.md file.
8
+
9
+################################################################################
10
+require(File.expand_path('../lib/test.rb', File.dirname(__FILE__)))
11
+
12
+################################################################################
13
+class RetryTest < MiniTest::Unit::TestCase
14
+
15
+  ##############################################################################
16
+  class VendorDeadlockError < StandardError; end
17
+
18
+  ##############################################################################
19
+  # Fake sleep so the tests don't take forever.
20
+  def sleep (sec)
21
+    # Don't really sleep ;)
22
+  end
23
+
24
+  ##############################################################################
25
+  def first_stab
26
+    service = MiniTest::Mock.new
27
+    service.expect(:update, nil, [nil])
28
+    record = nil
29
+
30
+    # <<: bad
31
+    begin
32
+      service.update(record)
33
+    rescue VendorDeadlockError
34
+      sleep(15)
35
+      retry
36
+    end
37
+    # :>>
38
+
39
+    service.verify
40
+    return 1
41
+  end
42
+
43
+  ##############################################################################
44
+  def rescue_loop
45
+    service = MiniTest::Mock.new
46
+    service.expect(:update, nil, [nil])
47
+    record = nil
48
+
49
+    # <<: while
50
+    while true
51
+      begin
52
+        service.update(record)
53
+      rescue VendorDeadlockError
54
+        sleep(15)
55
+        # Drop exception
56
+      else
57
+        break # Success, exit loop.
58
+      end
59
+    end
60
+    # :>>
61
+
62
+    service.verify
63
+    return 1
64
+  end
65
+
66
+  ##############################################################################
67
+  def bounded
68
+    service = Object.new
69
+    def service.update (x) raise(VendorDeadlockError); end
70
+    record = nil
71
+
72
+    # <<: bounded
73
+    retries = 0
74
+
75
+    begin
76
+      service.update(record)
77
+    rescue VendorDeadlockError
78
+      raise if retries >= 3
79
+      retries += 1
80
+      sleep(15)
81
+      retry
82
+    end
83
+    # :>>
84
+  end
85
+
86
+  ##############################################################################
87
+  def backoff
88
+    record = nil
89
+    service = Object.new
90
+    def service.update (x) raise(VendorDeadlockError); end
91
+
92
+    logger = Object.new
93
+    def logger.warn (x); end
94
+
95
+    # <<: backoff
96
+    retries = 0
97
+
98
+    begin
99
+      service.update(record)
100
+    rescue VendorDeadlockError => e
101
+      raise if retries >= 3
102
+      retries += 1
103
+      logger.warn("API failure: #{e}, retrying...")
104
+      sleep(5 ** retries)
105
+      retry
106
+    end
107
+    # :>>
108
+  end
109
+
110
+  ##############################################################################
111
+  def test_a_bunch_of_stuff
112
+    assert_equal(1, first_stab)
113
+    assert_equal(1, rescue_loop)
114
+    assert_raises(VendorDeadlockError) {bounded}
115
+    assert_raises(VendorDeadlockError) {backoff}
116
+  end
117
+end

+ 174
- 0
exceptions/specific_test.rb View File

@@ -0,0 +1,174 @@
1
+################################################################################
2
+# This file is part of the package effrb. It is subject to the license
3
+# terms in the LICENSE.md file found in the top-level directory of
4
+# this distribution and at https://github.com/pjones/effrb. No part of
5
+# the effrb package, including this file, may be copied, modified,
6
+# propagated, or distributed except according to the terms contained
7
+# in the LICENSE.md file.
8
+
9
+################################################################################
10
+require(File.expand_path('../lib/test.rb', File.dirname(__FILE__)))
11
+
12
+################################################################################
13
+class SpecificTest < MiniTest::Unit::TestCase
14
+
15
+  ##############################################################################
16
+  class NetworkConnectionError < StandardError; end
17
+  class InvalidRecordError < StandardError; end
18
+
19
+  ##############################################################################
20
+  def default_rescue
21
+    logger = MiniTest::Mock.new
22
+    logger.expect(:error, nil, [String])
23
+
24
+    task = Object.new
25
+    def task.perform; raise(RuntimeError); end
26
+
27
+    # <<: default
28
+    begin
29
+      task.perform
30
+    rescue => e
31
+      logger.error("task failed: #{e}")
32
+      # Swallow exception, abort task.
33
+    end
34
+    # :>>
35
+
36
+    logger.verify
37
+    return 1
38
+  end
39
+
40
+  ##############################################################################
41
+  def blacklist
42
+    logger = MiniTest::Mock.new
43
+    logger.expect(:error, nil, [String])
44
+
45
+    task = Object.new
46
+    def task.perform; raise(RuntimeError); end
47
+
48
+    # <<: blacklist
49
+    begin
50
+      task.perform
51
+    rescue ArgumentError, LocalJumpError,
52
+           NoMethodError, ZeroDivisionError
53
+      # Don't actually handle these.
54
+      raise
55
+    rescue => e
56
+      logger.error("task failed: #{e}")
57
+      # Swallow exception, abort task.
58
+    end
59
+    # :>>
60
+
61
+    logger.verify
62
+    return 1
63
+  end
64
+
65
+  ##############################################################################
66
+  def whitelist
67
+    task = Object.new
68
+    def task.perform; raise(NetworkConnectionError); end
69
+
70
+    # <<: whitelist
71
+    begin
72
+      task.perform
73
+    rescue NetworkConnectionError => e
74
+      # Retry logic...
75
+    rescue InvalidRecordError => e
76
+      # Send record to support staff...
77
+    end
78
+    # :>>
79
+
80
+    return 1
81
+  end
82
+
83
+  ##############################################################################
84
+  def whitelist_wide
85
+    service = MiniTest::Mock.new
86
+    service.expect(:record, nil, [RuntimeError])
87
+
88
+    task = Object.new
89
+    def task.perform; raise(RuntimeError); end
90
+
91
+    # <<: whitelist-wide
92
+    begin
93
+      task.perform
94
+    rescue NetworkConnectionError => e
95
+      # Retry logic...
96
+    rescue InvalidRecordError => e
97
+      # Send record to support staff...
98
+    rescue => e
99
+      service.record(e)
100
+      raise
101
+    ensure
102
+      # ...
103
+    end
104
+    # :>>
105
+
106
+  rescue
107
+    service.verify
108
+    return 1
109
+  end
110
+
111
+  ##############################################################################
112
+  def generate_eof
113
+    file = File.open("/dev/null")
114
+    config = MiniTest::Mock.new
115
+    config.expect(:parse_line, nil, [String])
116
+
117
+    # <<: specific
118
+    begin
119
+      while line = file.readline
120
+        config.parse_line(line)
121
+      end
122
+    rescue EOFError
123
+      # Ignore EOF.
124
+    end
125
+    # :>>
126
+
127
+    # config.verify: It won't actually be called.
128
+    return 1
129
+  end
130
+
131
+  ##############################################################################
132
+  def in_order
133
+    # <<: order
134
+    begin
135
+      # ...
136
+    rescue EOFError
137
+      # ...
138
+    rescue Exception => e
139
+      logger.error("failure: #{e}")
140
+      raise
141
+    end
142
+    # :>>
143
+
144
+    return 1
145
+  end
146
+
147
+  ##############################################################################
148
+  # <<: recover
149
+  def send_to_support_staff (e)
150
+    # ...
151
+  rescue
152
+    raise(e)
153
+  end
154
+  # :>>
155
+
156
+  ##############################################################################
157
+  def send_to_support_staff_wrapper
158
+    raise(NetworkConnectionError)
159
+  rescue NetworkConnectionError => e
160
+    send_to_support_staff(e)
161
+    return 1
162
+  end
163
+
164
+  ##############################################################################
165
+  def test_a_lot_of_things
166
+    assert_equal(1, default_rescue)
167
+    assert_equal(1, generate_eof)
168
+    assert_equal(1, in_order)
169
+    assert_equal(1, blacklist)
170
+    assert_equal(1, whitelist)
171
+    assert_equal(1, whitelist_wide)
172
+    assert_equal(1, send_to_support_staff_wrapper)
173
+  end
174
+end

+ 128
- 0
exceptions/strings_test.rb View File

@@ -0,0 +1,128 @@
1
+################################################################################
2
+# This file is part of the package effrb. It is subject to the license
3
+# terms in the LICENSE.md file found in the top-level directory of
4
+# this distribution and at https://github.com/pjones/effrb. No part of
5
+# the effrb package, including this file, may be copied, modified,
6
+# propagated, or distributed except according to the terms contained
7
+# in the LICENSE.md file.
8
+
9
+################################################################################
10
+require(File.expand_path('../lib/test.rb', File.dirname(__FILE__)))
11
+
12
+################################################################################
13
+class StringsTest < MiniTest::Unit::TestCase
14
+
15
+  ##############################################################################
16
+  # <<: weak
17
+  class CoffeeTooWeakError < StandardError; end
18
+  # :>>
19
+
20
+  ##############################################################################
21
+  # <<: temp
22
+  class TemperatureError < StandardError
23
+    attr_reader(:temperature)
24
+
25
+    def initialize (temperature)
26
+      @temperature = temperature
27
+      super("invalid temperature: #@temperature")
28
+    end
29
+  end
30
+  # :>>
31
+
32
+  ##############################################################################
33
+  def weak_rescue
34
+    coffee = MiniTest::Mock.new
35
+
36
+    def coffee.drink
37
+      raise(CoffeeTooWeakError)
38
+    end
39
+
40
+    logger = MiniTest::Mock.new
41
+    logger.expect(:error, nil, [String])
42
+
43
+    # <<: rescue-weak
44
+    begin
45
+      coffee.drink # Try to enjoy it.
46
+    rescue CoffeeTooWeakError
47
+      logger.error("another bad coffee")
48
+      raise
49
+    end
50
+    # :>>
51
+
52
+  rescue CoffeeTooWeakError
53
+    coffee.verify
54
+    logger.verify
55
+    return 1
56
+  end
57
+
58
+  ##############################################################################
59
+  def test_basic_raise
60
+    assert_raises(RuntimeError) do
61
+      # <<: string
62
+      raise("coffee machine low on water")
63
+      # :>>
64
+    end
65
+
66
+    assert_raises(RuntimeError) do
67
+      # <<: runtime
68
+      raise(RuntimeError, "coffee machine low on water")
69
+      # :>>
70
+    end
71
+  end
72
+
73
+  ##############################################################################
74
+  def test_weak
75
+    assert_raises(CoffeeTooWeakError) do
76
+      # <<: raise-weak-simple
77
+      raise(CoffeeTooWeakError)
78
+      # :>>
79
+    end
80
+
81
+    assert_raises(CoffeeTooWeakError) do
82
+      # <<: raise-weak-string
83
+      raise(CoffeeTooWeakError, "coffee to water ratio too low")
84
+      # :>>
85
+    end
86
+
87
+    assert_equal(1, weak_rescue)
88
+  end
89
+
90
+  ##############################################################################
91
+  def test_raise_temp_no_args
92
+    assert_raises(ArgumentError) do
93
+      raise(TemperatureError)
94
+    end
95
+  end
96
+
97
+  ##############################################################################
98
+  def test_temp_ex
99
+    assert_raises(TemperatureError) do
100
+      # <<: raise-temp
101
+      raise(TemperatureError.new(180))
102
+      # :>>
103
+    end
104
+
105
+    caught = false
106
+
107
+    begin
108
+      raise(TemperatureError.new(99))
109
+    rescue TemperatureError => e
110
+      assert_equal(99, e.temperature)
111
+      assert_equal("invalid temperature: 99", e.message)
112
+      caught = true
113
+    rescue
114
+      assert(false)
115
+    end
116
+
117
+    assert(caught)
118
+  end
119
+
120
+  ##############################################################################
121
+  def test_temp_with_string
122
+    begin
123
+      raise(TemperatureError.new(100), "boom!")
124
+    rescue TemperatureError => e
125
+      assert_equal(100, e.temperature)
126
+    end
127
+  end
128
+end

+ 88
- 0
exceptions/throw_test.rb View File