remotely_exceptional 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5db0afaa461aa92d936cae11184f651757c23e50
4
+ data.tar.gz: 6b8dfd4d4a3ff0c8602e435126de50e8ccc5dc70
5
+ SHA512:
6
+ metadata.gz: 5697edf8df114e0a6c5c1953f76ae740e23dd69e6fc552dda3198e03e14a3a198f57d2e43f70be73fc5410afe2fc969333a20162d4190e333eb8160ac47fd084
7
+ data.tar.gz: 2a3ed81301611e4cff979f3a20b7ad7335d749b7751e7066a0cb95c70493334350f9d32b30b6f6245eb088a82ec7185229240d008071f72fe78a81b60622602b
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+
3
+ rvm:
4
+ - 2.0.0
5
+ - 2.1.0
6
+ - 2.2.0
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :doc do
6
+ gem "yard"
7
+ end
8
+
9
+ group :test do
10
+ gem "coveralls", :require => false
11
+ gem "guard"
12
+ gem "guard-minitest"
13
+ gem "minitest", ">= 3.0"
14
+ gem "mocha"
15
+ gem "simplecov", :require => false
16
+ end
data/Guardfile ADDED
@@ -0,0 +1,7 @@
1
+ gem_name = File.basename(Dir[File.expand_path("../*.gemspec", __FILE__)].first)[0..-9]
2
+
3
+ guard(:minitest, :all_after_pass => false, :all_on_start => false) do
4
+ watch(%r{^lib/#{gem_name}/(.+)\.rb$}) { |m| "test/unit/#{m[1]}_test.rb" }
5
+ watch(%r{^test/.+_test\.rb$})
6
+ watch(%r{^(?:test/test_helper(.*)|lib/#{gem_name})\.rb$}) { "test" }
7
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Danny Guinther
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
data/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # RemotelyExceptional
2
+ [![Gem Version](https://badge.fury.io/rb/remotely_exceptional.svg)](http://badge.fury.io/rb/remotely_exceptional)
3
+ [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/gems/remotely_exceptional)
4
+ [![Build Status](https://travis-ci.org/tdg5/remotely_exceptional.svg)](https://travis-ci.org/tdg5/remotely_exceptional)
5
+ [![Coverage Status](https://coveralls.io/repos/tdg5/remotely_exceptional/badge.svg)](https://coveralls.io/r/tdg5/remotely_exceptional)
6
+ [![Code Climate](https://codeclimate.com/github/tdg5/remotely_exceptional/badges/gpa.svg)](https://codeclimate.com/github/tdg5/remotely_exceptional)
7
+ [![Dependency Status](https://gemnasium.com/tdg5/remotely_exceptional.svg)](https://gemnasium.com/tdg5/remotely_exceptional)
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem "remotely_exceptional"
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ ```bash
20
+ $ bundle
21
+ ```
22
+
23
+ Or install it yourself as:
24
+
25
+ ```bash
26
+ $ gem install remotely_exceptional
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ ## Contributing
32
+
33
+ 1. Fork it ( https://github.com/tdg5/remotely_exceptional/fork )
34
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
35
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
36
+ 4. Push to the branch (`git push origin my-new-feature`)
37
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << "test"
6
+ t.pattern = "test/**/*_test.rb"
7
+ end
8
+
9
+ task :default => :test
@@ -0,0 +1,11 @@
1
+ require "remotely_exceptional/version"
2
+ require "remotely_exceptional/exceptions"
3
+ require "remotely_exceptional/handler"
4
+ require "remotely_exceptional/remote_handling"
5
+
6
+ # The namespace for the RemotelyExceptional gem.
7
+ module RemotelyExceptional
8
+ # The namespace that specialized handlers live in.
9
+ module Handlers
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ module RemotelyExceptional
2
+ class Error < ::RuntimeError
3
+ end
4
+
5
+ class InvalidHandlerResponse < RemotelyExceptional::Error
6
+ attr_accessor :original_exception
7
+ def initialize(*)
8
+ super
9
+ @original_exception = $!
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,91 @@
1
+ # Mixin providing basic functionality required for matching and handling
2
+ # exceptions.
3
+ module RemotelyExceptional::Handler
4
+ # Actions that will be taken on any object that includes this module.
5
+ #
6
+ # @param includer [Class,Module] The class or module that has included this
7
+ # module.
8
+ def self.included(includer)
9
+ includer.extend(ClassMethods)
10
+ end
11
+
12
+ # Factory function for creating classes with Handler behaviors. Creates a new
13
+ # class with Handler behaviors from the given super class and block. By
14
+ # default the super class of the new class will be Object. The given block
15
+ # will be used as the matcher of the generated class.
16
+ #
17
+ # @param super_class [Class] An optional super class to use when creating a
18
+ # new class with Handler behaviors.
19
+ # @yieldparam [Exception] exception_instance The exception instance that
20
+ # should be evaluated for a match.
21
+ # @yieldreturn [Boolean] A boolean value indicating whether or not the
22
+ # exception instance was matched.
23
+ # @return [Class] Returns a new class extended with Handler behaviors.
24
+ def self.new(super_class = Object, &block)
25
+ raise ArgumentError, "Block required" unless block_given?
26
+ handler_class = Class.new(super_class)
27
+ handler_class.send(:include, self)
28
+ handler_class.instance_variable_set(:@matcher, block)
29
+ handler_class
30
+ end
31
+
32
+ # Class-level handler behaviors that will be added to any object that includes
33
+ # this module.
34
+ module ClassMethods
35
+ # Used by Ruby's rescue keyword to evaluate if an exception instance can be
36
+ # caught by this Class or Module. Delegates to {#matcher}.
37
+ #
38
+ # @param exception [Exception] The exception instance that should be evaluated
39
+ # for a match.
40
+ # @return [Boolean] Returns a Boolean value indicating whether or not the
41
+ # exception instance matches this handler.
42
+ def ===(exception)
43
+ matcher.call(exception)
44
+ end
45
+
46
+ # Factory method that takes in an exception and an optional Hash of
47
+ # additional contextual information and creates a new Handler instance from
48
+ # that data. The generated Handler instance is then used to handle the the
49
+ # exception.
50
+ #
51
+ # @param exception [Exception] The exception to handle. Defaults to $!.
52
+ # @param context [Hash{Symbol=>Object}] An optional Hash of additional
53
+ # contextual information about the exception.
54
+ # @return [Symbol] Returns a symbol indicating what action should be taken
55
+ # to continue execution. Depending on the situation, valid values include:
56
+ # [:continue, :raise, :retry]
57
+ def handle(exception = $!, context = {})
58
+ instance = new
59
+ context, exception = exception, $! if exception.is_a?(Hash)
60
+ instance.instance_variable_set(:@exception, exception)
61
+ instance.instance_variable_set(:@context, context)
62
+ instance.handle
63
+ end
64
+
65
+ private
66
+
67
+ # The block used by the class to evaluate matching exceptions.
68
+ def matcher
69
+ @matcher
70
+ end
71
+ end
72
+
73
+ attr_reader :context, :exception
74
+
75
+ # Placeholder method, must be implemented by including class. Should
76
+ # encapsulate the logic required to handle an exception matced by the class.
77
+ # Should take no arguments.
78
+ #
79
+ # @raise [NotImplementedError] Raised when the including class does not
80
+ # provide it's own #handle instance method.
81
+ # @return [Symbol] Returns a symbol indicating what action should be taken
82
+ # to continue execution. Depending on the situation, valid values include:
83
+ # [:continue, :raise, :retry]
84
+ # @return [Array<(Symbol, Object)>] Returns a symbol indicating what action
85
+ # should be taken to continue execution and an object that should be used as
86
+ # the result of the rescue operation. Depending on the situation, valid
87
+ # action values include: [:continue, :raise, :retry]
88
+ def handle
89
+ raise NotImplementedError, "#{__method__} must be implemented by including class!"
90
+ end
91
+ end
@@ -0,0 +1,178 @@
1
+ module RemotelyExceptional::Handlers::PrioritizedHandler
2
+ # The default priority that should be used when registering handlers.
3
+ DEFAULT_PRIORITY = 1000.freeze
4
+
5
+ # Hash#default_proc for hashes that should return a new Set if a given key is
6
+ # not defined.
7
+ HASH_BUILDER = lambda { |hash, key| hash[key] = Set.new }.freeze
8
+
9
+ def self.included(includer)
10
+ includer.extend(ClassMethods)
11
+ end
12
+
13
+ module ClassMethods
14
+ # Determines if any of the available handlers match the provided exception.
15
+ #
16
+ # @param exception [Exception] An exception.
17
+ # @return [Boolean] Returns true if a handler is available that matches the
18
+ # given exception. Returns false if none of the available handlers match the
19
+ # given exception.
20
+ def ===(exception)
21
+ !!handler_for_exception(exception)
22
+ end
23
+
24
+ # Returns the Hash of block handlers by priority.
25
+ #
26
+ # @return [Hash{Integer,Set}] The Hash of priorities and which block handlers
27
+ # belong to those priorities.
28
+ def block_handlers
29
+ Thread.current["#{name}.block_handlers"] ||= Hash.new(&HASH_BUILDER)
30
+ end
31
+
32
+ # The default priority level that should be used for handlers when no priority
33
+ # is provided.
34
+ #
35
+ # @return [Integer] The default priority level.
36
+ def default_priority
37
+ const_get(:DEFAULT_PRIORITY)
38
+ end
39
+
40
+ # Finds the handler with the highest priority that matches the exception and
41
+ # uses this handler to handle the exception. If no handlers are available that
42
+ # match the given exception, the exception is re-raised.
43
+ #
44
+ # @param exception [Exception] The exception to handle.
45
+ # @param context [Hash{Symbol=>Object}] An optional Hash of additional
46
+ # contextual information about the exception.
47
+ # @raise [exception] The given exception is reraised if no handler is found
48
+ # that matches the given exception.
49
+ # @return [Symbol] Returns a symbol indicating what action should be taken
50
+ # to continue execution. Depending on the situation, valid values include:
51
+ # [:continue, :raise, :retry]
52
+ def handle(exception = $!, context = {})
53
+ context, exception = exception, $! if exception.is_a?(Hash)
54
+ priority_handler = handler_for_exception(exception)
55
+ raise exception if !priority_handler
56
+ priority_handler.handle(exception, context)
57
+ end
58
+
59
+ # Finds the handler with the highest priority that matches the given
60
+ # exception. Returns nil if no matching handler can be found.
61
+ #
62
+ # @param exception [Exception] The exception to find a matching handler for.
63
+ # @return [RemotelyExceptional::Handler] Returns the handler with the
64
+ # highest priority that matches the given exception. If no handler is found,
65
+ # returns nil.
66
+ # @return [nil] Returns nil if no matching handler could be found.
67
+ def handler_for_exception(exception)
68
+ prioritized_handlers.detect { |handler| handler === exception }
69
+ end
70
+
71
+ # Returns an enumerator that yields block handlers and registered handlers in
72
+ # priority ASC, name ASC order. The collection is lazily generated, so changes
73
+ # to the sets of handlers may appear during traversal. If consistent state is
74
+ # necessary, force the returned enumerator to eagerly generate the full
75
+ # collection using #to_a or similar.
76
+ #
77
+ # @return [Enumerator<RemotelyExceptional::Handler>] An enumerator of all
78
+ # known block handlers and registered handlers in priority ASC, name ASC
79
+ # order.
80
+ def prioritized_handlers
81
+ Enumerator.new do |yielder|
82
+ priorities = (registered_handlers.keys | block_handlers.keys).sort!
83
+ priorities.uniq!
84
+ priorities.each do |priority|
85
+ if registered_handlers.key?(priority)
86
+ collected_handlers = registered_handlers[priority].to_a
87
+ end
88
+ if block_handlers.key?(priority)
89
+ temp_handlers = block_handlers[priority].to_a
90
+ collected_handlers &&= collected_handlers.concat(temp_handlers)
91
+ collected_handlers ||= temp_handlers
92
+ end
93
+ collected_handlers.sort_by!(&:name)
94
+ collected_handlers.uniq!
95
+ collected_handlers.each { |handler| yielder << handler }
96
+ end
97
+ end
98
+ end
99
+
100
+ # Adds the given handler to the set of registered handlers. Optionally, a
101
+ # priority may be supplied. If no priority is supplied the {::default_priority
102
+ # default priority} is used.
103
+ #
104
+ # @param handler [RemotelyExceptional::Handler] The handler that should be
105
+ # registered.
106
+ # @param options [Hash{Symbol=>Object}] A Hash of optional arguments.
107
+ # @option options [Integer] :priority ({::default_priority}) The priority of
108
+ # the handler.
109
+ # @return [Boolean] Returns true if the handler was successfully registered
110
+ # for the given priority. Returns false if the handler was already registered
111
+ # for the given priority.
112
+ def register_handler(handler, options = {})
113
+ priority = options[:priority] || default_priority
114
+ !!registered_handlers[priority].add?(handler)
115
+ end
116
+
117
+ # Returns the Hash of registered handlers by priority.
118
+ #
119
+ # @return [Hash{Integer,Set<RemotelyExceptional::Handler>}] The Hash of
120
+ # priorities and which handlers belong to those priorities.
121
+ def registered_handlers
122
+ Thread.current["#{name}.registered_handlers"] ||= Hash.new(&HASH_BUILDER)
123
+ end
124
+
125
+ # Removes the given handler. By default removes the handler from the
126
+ # {::default_priority default_priority}, but a :priority option may be
127
+ # supplied to remove the handler from a specified priority.
128
+ #
129
+ # @param handler [RemotelyExceptional::Handler] The handler that should be
130
+ # removed.
131
+ # @param options [Hash{Symbol=>Object}] A Hash of optional arguments.
132
+ # @option options [Integer] :priority ({::default_priority}) The priority that
133
+ # should be searched for the given handler.
134
+ # @return [Boolean] Returns true if the handler was successfully removed for
135
+ # the given priority. Returns false if the handler was not registered for
136
+ # the given priority.
137
+ def remove_handler(handler, options = {})
138
+ priority = options[:priority] || default_priority
139
+ registered_handlers.key?(priority) &&
140
+ !!registered_handlers[priority].delete(handler)
141
+ end
142
+
143
+ # Clears all {::block_handlers block handlers} and {::registered_handlers
144
+ # registered handlers}.
145
+ #
146
+ # @return [true]
147
+ def reset_handlers!
148
+ Thread.current["#{name}.registered_handlers"] = nil
149
+ Thread.current["#{name}.block_handlers"] = nil
150
+ true
151
+ end
152
+
153
+ # Registers a handler for the duration of the given block. By default
154
+ # registers the block at the {::default_priority default priority}, but a
155
+ # specific priority may be supplied as an option.
156
+ #
157
+ # @param handler [RemotelyExceptional::Handler] The handler that should be
158
+ # registered.
159
+ # @param options [Hash{Symbol=>Object}] A Hash of optional arguments.
160
+ # @option options [Integer] :priority ({::default_priority}) The priority that
161
+ # should be used to register the handler.
162
+ # @raise [ArgumentError] if a block is not provided.
163
+ # @return [Boolean] Returns true if the block handler was successfully
164
+ # registered for the given priority. Returns false if a matching block
165
+ # handler was already registered for the given priority.
166
+ def with_handler(handler, options = {})
167
+ raise ArgumentError, "Block required!" unless block_given?
168
+
169
+ priority = options[:priority] || default_priority
170
+ if block_handlers[priority].add?(handler)
171
+ added_handler = true
172
+ end
173
+ yield
174
+ ensure
175
+ block_handlers[priority].delete(handler) if added_handler
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,42 @@
1
+ module RemotelyExceptional::RemoteHandling
2
+ # Executes the given block of code in a context that allows for remote
3
+ # handling of exceptions using the specified handler class. Optionally,
4
+ # additional contextual information may be provided in case of an exception.
5
+ #
6
+ # @param handler [RemotelyExceptional::Handler] The handler that should be
7
+ # used to match and handle any exceptions that occur.
8
+ # @param context [Hash{Symbol=>Object}] Optional contextual information that
9
+ # will be made available to the handler if an exception occurs.
10
+ # @raise [ArgumentError] Raised if the provided handler is not a valid
11
+ # RemotelyExceptional::Handler.
12
+ # @raise [RemotelyExceptional::InvalidHandlerResponse] Raised if an exception
13
+ # is raised but the handler does not return a valid action symbol.
14
+ # @raise [Exception] Depending on the Handler used, could raise any error
15
+ # returned by the handler's handle method.
16
+ # @return [Object] Returns the result of the given block if no exception
17
+ # occurs.
18
+ # @return [Object, nil] If an exception occurs may return a result value
19
+ # provided by the exception handler's handle method. If the handler's handle
20
+ # method does not specify a result, nil will be returned instead.
21
+ def remotely_exceptional(handler, context = {})
22
+ raise ArgumentError, "Invalid Handler! Got #{handler.inspect}" unless handler &&
23
+ handler.respond_to?(:ancestors) &&
24
+ handler.ancestors.include?(RemotelyExceptional::Handler)
25
+
26
+ # Must explicitly use begin otherwise TypeError will occur if handler is not
27
+ # a Class or Module. We can raise a more specific error if begin is used.
28
+ begin
29
+ yield
30
+ rescue handler
31
+ response_code, result = handler.handle(context)
32
+ case response_code
33
+ when :raise then result ? raise(result) : raise
34
+ when :retry then retry
35
+ when :continue then result
36
+ else
37
+ msg = "Handler did not return an expected response code!"
38
+ raise RemotelyExceptional::InvalidHandlerResponse, msg
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,4 @@
1
+ module RemotelyExceptional
2
+ # The version of the RemotelyExceptional gem
3
+ VERSION = "0.0.1"
4
+ end
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "remotely_exceptional/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "remotely_exceptional"
8
+ spec.version = RemotelyExceptional::VERSION
9
+ spec.authors = ["Danny Guinther"]
10
+ spec.email = ["dannyguinther@gmail.com"]
11
+ spec.summary = %q{Remote control of exceptions raised in distant contexts.}
12
+ spec.description = %q{Remote control of exceptions raised in distant contexts.}
13
+ spec.homepage = "https://github.com/tdg5/remotely_exceptional"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^test/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ spec.add_development_dependency "rake", "~> 0"
23
+ end
@@ -0,0 +1,23 @@
1
+ if ENV["CI"]
2
+ require "simplecov"
3
+ require "coveralls"
4
+ SimpleCov.formatter = Coveralls::SimpleCov::Formatter
5
+ SimpleCov.root(File.expand_path("../..", __FILE__))
6
+ SimpleCov.start do
7
+ add_filter "test"
8
+ end
9
+ end
10
+
11
+ require "minitest/autorun"
12
+ require "mocha/setup"
13
+ require "remotely_exceptional"
14
+
15
+ # Use alternate shoulda-style DSL for tests
16
+ class RemotelyExceptional::TestCase < Minitest::Spec
17
+ class << self
18
+ alias :setup :before
19
+ alias :teardown :after
20
+ alias :context :describe
21
+ alias :should :it
22
+ end
23
+ end
@@ -0,0 +1,22 @@
1
+ require "test_helper"
2
+
3
+ class RemotelyExceptional::ExceptionsTest < RemotelyExceptional::TestCase
4
+ exception = RemotelyExceptional::InvalidHandlerResponse
5
+ context exception.name do
6
+ subject { exception }
7
+ context "#original_exception" do
8
+ should "capture the original exception if one exists" do
9
+ assert_nil subject.new.original_exception
10
+ exception = ArgumentError
11
+ rescued = false
12
+ begin
13
+ raise exception
14
+ rescue exception
15
+ rescued = true
16
+ assert_kind_of exception, subject.new.original_exception
17
+ end
18
+ assert_equal true, rescued
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,135 @@
1
+ require "test_helper"
2
+
3
+ class RemotelyExceptional::HandlerTest < RemotelyExceptional::TestCase
4
+ Subject = RemotelyExceptional::Handler
5
+ class TestMixer
6
+ include Subject
7
+ end
8
+
9
+ context Subject.name do
10
+ subject { Subject }
11
+
12
+ context "::new" do
13
+ should "raise ArgumentError if no block was given" do
14
+ assert_raises(ArgumentError) do
15
+ subject.new
16
+ end
17
+ end
18
+
19
+ should "return a new class that includes #{Subject.name}" do
20
+ test_class = subject.new {|other| true }
21
+ assert_equal true, test_class.ancestors.include?(subject)
22
+ assert_kind_of subject, test_class.new
23
+ end
24
+
25
+ should "set the matcher to the given block" do
26
+ block = proc {|other| true }
27
+ test_class = subject.new(&block)
28
+ assert_equal block, test_class.send(:matcher)
29
+ end
30
+
31
+ should "take an optional super_class argument" do
32
+ test_super_class = Class.new
33
+ test_class = subject.new(test_super_class) {|other| true }
34
+ assert_kind_of test_super_class, test_class.new
35
+ end
36
+ end
37
+ end
38
+
39
+ context TestMixer.name do
40
+ subject { TestMixer }
41
+
42
+ context "included behaviors" do
43
+ context "::new" do
44
+ should "not be overriden by the module" do
45
+ assert_kind_of subject, subject.new
46
+ end
47
+ end
48
+
49
+ context "::===" do
50
+ should "call the matcher with the given argument" do
51
+ expected_argument = ArgumentError
52
+ subject.expects(:matcher).returns(lambda { |other| other == expected_argument })
53
+ assert_equal true, subject === expected_argument
54
+ end
55
+ end
56
+
57
+ context "::handle" do
58
+ setup do
59
+ @exception = ArgumentError.new
60
+ @context = { :context => true }
61
+ @instance = subject.new
62
+ subject.expects(:new).at_least(1).returns(@instance)
63
+ end
64
+
65
+ should "create a new instance and invoke #handle" do
66
+ @instance.expects(:handle)
67
+ subject.handle(@exception, @context)
68
+ end
69
+
70
+ should "set the exception and context of the instance correctly" do
71
+ @instance.stubs(:handle)
72
+ subject.handle(@exception, @context)
73
+ assert_equal @exception, @instance.exception
74
+ assert_equal @context, @instance.context
75
+ end
76
+
77
+ should "should automatically detect exception if not provided in an exception context" do
78
+ @instance.stubs(:handle)
79
+ begin
80
+ raise @exception
81
+ rescue
82
+ subject.handle(@context)
83
+ end
84
+ # Should be the exception in an exception context
85
+ assert_equal @context, @instance.context
86
+ assert_equal @exception, @instance.exception
87
+ end
88
+
89
+ should "have a nil exception when not provided an exception outside of an exception context" do
90
+ @instance.stubs(:handle)
91
+ subject.handle(@context)
92
+ assert_equal @context, @instance.context
93
+ # Should be nil outside of an exception context
94
+ assert_nil @instance.exception
95
+ end
96
+ end
97
+
98
+ context "rescue" do
99
+ should "call the matcher with an exception class when used to rescue" do
100
+ expected_argument = ArgumentError
101
+ subject.expects(:matcher).returns(lambda { |other| !other.is_a?(expected_argument) })
102
+
103
+ rescued = false
104
+ assert_raises(ArgumentError) do
105
+ begin
106
+ raise ArgumentError
107
+ rescue subject
108
+ rescued = true
109
+ end
110
+ end
111
+ assert_equal false, rescued
112
+ end
113
+
114
+ should "invoke rescue code when the matcher matches the exception" do
115
+ expected_argument = ArgumentError
116
+ subject.expects(:matcher).returns(lambda { |other| other.is_a?(expected_argument) })
117
+
118
+ rescued = false
119
+ begin
120
+ raise ArgumentError
121
+ rescue subject
122
+ rescued = true
123
+ end
124
+ assert_equal true, rescued
125
+ end
126
+ end
127
+
128
+ context "#handle" do
129
+ should "raise NotImplementedError if not overriden" do
130
+ assert_raises(NotImplementedError) { subject.new.handle }
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,297 @@
1
+ require "test_helper"
2
+ require "remotely_exceptional/handlers/prioritized_handler"
3
+
4
+ class RemotelyExceptional::Handlers::PrioritizedHandlerTest < RemotelyExceptional::TestCase
5
+ Subject = RemotelyExceptional::Handlers::PrioritizedHandler
6
+
7
+ AlphaHandler = RemotelyExceptional::Handler.new { |ex| ArgumentError === ex }
8
+ BetaHandler = RemotelyExceptional::Handler.new { |ex| RuntimeError === ex }
9
+ OmegaHandler = RemotelyExceptional::Handler.new { |ex| Exception === ex }
10
+
11
+ class TestSubject
12
+ include Subject
13
+ end
14
+
15
+ context "module that includes #{Subject.name}" do
16
+ subject { TestSubject }
17
+
18
+ setup { subject.reset_handlers! }
19
+
20
+ context "::===" do
21
+ should "return true if a handler is registered that matches the exception" do
22
+ subject.register_handler(AlphaHandler)
23
+ assert_equal true, subject === ArgumentError.new
24
+ end
25
+
26
+ should "return false if none of the handlers match the exception" do
27
+ subject.register_handler(AlphaHandler)
28
+ assert_equal false, subject === RuntimeError.new
29
+ end
30
+ end
31
+
32
+ context "::default_priority" do
33
+ should "return 1000" do
34
+ assert_equal 1000, subject.default_priority
35
+ end
36
+ end
37
+
38
+ context "::handle" do
39
+ should "delegate handling to the matching handler with the highest priority" do
40
+ subject.register_handler(AlphaHandler)
41
+ subject.register_handler(OmegaHandler, :priority => 10)
42
+ ex = ArgumentError.new
43
+ result = :continue
44
+ context = { :context => :foo }
45
+ OmegaHandler.expects(:handle).with(ex, context).returns(result)
46
+ assert_equal result, subject.handle(ex, context)
47
+ end
48
+
49
+ should "re-raise the exception if no matching handler is found" do
50
+ ex = ArgumentError.new
51
+ assert_raises(ex.class) { subject.handle(ex) }
52
+ end
53
+ end
54
+
55
+ context "::handler_for_exception" do
56
+ should "return the matching handler" do
57
+ subject.register_handler(AlphaHandler)
58
+ assert_equal AlphaHandler, subject.handler_for_exception(ArgumentError.new)
59
+ end
60
+
61
+ should "return the matching handler with the highest priority" do
62
+ subject.register_handler(AlphaHandler)
63
+ subject.register_handler(OmegaHandler, :priority => 10)
64
+ assert_equal OmegaHandler, subject.handler_for_exception(ArgumentError.new)
65
+ end
66
+
67
+ should "return nil if no matching handler is found" do
68
+ subject.register_handler(AlphaHandler)
69
+ subject.register_handler(BetaHandler)
70
+ assert_nil subject.handler_for_exception(SystemStackError.new)
71
+ end
72
+ end
73
+
74
+ context "::prioritized_handlers" do
75
+ should "yield handlers in priority ASC, name ASC order" do
76
+ setup_registered_handlers(subject)
77
+ found_handlers = nil
78
+ # Should be yielded first
79
+ subject.with_handler(BetaHandler, :priority => 5) do
80
+ # Should be yielded last
81
+ subject.with_handler(OmegaHandler, :priority => 2500) do
82
+ # Should only be yielded once for this priority
83
+ subject.with_handler(AlphaHandler) do
84
+ found_handlers = subject.prioritized_handlers.to_a
85
+ end
86
+ end
87
+ end
88
+ @expected_handlers.unshift(BetaHandler)
89
+ @expected_handlers.push(OmegaHandler)
90
+ assert_equal @expected_handlers, found_handlers
91
+ end
92
+
93
+ should "work when there are only registered handlers" do
94
+ setup_registered_handlers(subject)
95
+ subject.instance_variable_set(:@block_handlers, nil)
96
+ assert_equal @expected_handlers, subject.prioritized_handlers.to_a
97
+ end
98
+
99
+ should "work when there are only block handlers" do
100
+ found_handlers = nil
101
+ subject.with_handler(BetaHandler, :priority => 5) do
102
+ subject.with_handler(OmegaHandler, :priority => 2500) do
103
+ subject.with_handler(AlphaHandler) do
104
+ found_handlers = subject.prioritized_handlers.to_a
105
+ end
106
+ end
107
+ end
108
+ expected_handlers = [
109
+ BetaHandler,
110
+ AlphaHandler,
111
+ OmegaHandler,
112
+ ]
113
+ assert_equal expected_handlers, found_handlers
114
+ end
115
+
116
+ should "yield a handler only once per priority level" do
117
+ subject.register_handler(AlphaHandler)
118
+ found_handlers = nil
119
+ subject.with_handler(AlphaHandler) do
120
+ found_handlers = subject.prioritized_handlers.to_a
121
+ end
122
+ assert_equal 1, found_handlers.length
123
+ assert_equal AlphaHandler, found_handlers.first
124
+ end
125
+
126
+ context "set scan" do
127
+ should "not create empty sets when no keys exist" do
128
+ subject.prioritized_handlers.to_a
129
+ assert_empty subject.send(:registered_handlers)
130
+ assert_empty subject.send(:block_handlers)
131
+ end
132
+
133
+ should "not create empty sets when a key exists in one set" do
134
+ priority = 1000
135
+ subject.with_handler(AlphaHandler, :priority => priority) do
136
+ subject.prioritized_handlers.to_a
137
+ end
138
+ assert_equal false, subject.send(:registered_handlers).key?(priority)
139
+
140
+ priority = 10
141
+ subject.register_handler(AlphaHandler, :priority => priority)
142
+ assert_equal false, subject.send(:block_handlers).key?(priority)
143
+ end
144
+ end
145
+ end
146
+
147
+ context "::register_handler" do
148
+ should "return false if the handler was not registered" do
149
+ priority = 10
150
+ assert_equal true, subject.register_handler(AlphaHandler, :priority => priority)
151
+ assert_equal false, subject.register_handler(AlphaHandler, :priority => priority)
152
+ end
153
+
154
+ should "return true if the handler was registered" do
155
+ priority = 10
156
+ assert_equal true, subject.register_handler(AlphaHandler, :priority => priority)
157
+ end
158
+
159
+ should "register the handler with the provided priority" do
160
+ priority = 10
161
+ registered_handlers = subject.send(:registered_handlers)
162
+ registered_handlers.expects(:[]).with(priority).returns(Set.new)
163
+ assert_equal true, subject.register_handler(AlphaHandler, :priority => priority)
164
+ end
165
+
166
+ should "register the handler with the default priority if no priority given" do
167
+ priority = subject.default_priority
168
+ registered_handlers = subject.send(:registered_handlers)
169
+ registered_handlers.expects(:[]).with(priority).returns(Set.new)
170
+ assert_equal true, subject.register_handler(AlphaHandler)
171
+ end
172
+ end
173
+
174
+ context "::remove_handler" do
175
+ should "return false if the handler was not removed" do
176
+ assert_equal false, subject.remove_handler(AlphaHandler)
177
+ end
178
+
179
+ should "return true if the handler was removed" do
180
+ assert_equal true, subject.register_handler(AlphaHandler)
181
+ assert_equal true, subject.remove_handler(AlphaHandler)
182
+ end
183
+
184
+ should "remove a handler with a matching priority" do
185
+ priority = 10
186
+ assert_equal true, subject.register_handler(AlphaHandler, :priority => priority)
187
+ assert_equal true, subject.remove_handler(AlphaHandler, :priority => priority)
188
+ end
189
+
190
+ should "not remove a handler with a non-matching priority" do
191
+ priority = 10
192
+ assert_equal true, subject.register_handler(AlphaHandler, :priority => priority)
193
+ assert_equal false, subject.remove_handler(AlphaHandler)
194
+ end
195
+ end
196
+
197
+ context "::reset_handlers!" do
198
+ should "clear all handlers" do
199
+ subject.register_handler(AlphaHandler, :priority => 10)
200
+ initial_found_handlers = final_found_handlers = nil
201
+ subject.with_handler(AlphaHandler) do
202
+ initial_found_handlers = subject.prioritized_handlers.to_a
203
+ subject.reset_handlers!
204
+ final_found_handlers = subject.prioritized_handlers.to_a
205
+ end
206
+ assert_equal 2, initial_found_handlers.length
207
+ assert_equal 0, final_found_handlers.length
208
+ end
209
+ end
210
+
211
+ context "::with_handler" do
212
+ should "raise ArgumentError if no block is given" do
213
+ assert_raises(ArgumentError) do
214
+ subject.with_handler(AlphaHandler)
215
+ end
216
+ end
217
+
218
+ should "register the handler if it is not already registered" do
219
+ assert_equal true, subject.prioritized_handlers.none?
220
+ subject.with_handler(AlphaHandler) do
221
+ found_handlers = subject.prioritized_handlers.to_a
222
+ assert_equal 1, found_handlers.length
223
+ assert_equal AlphaHandler, subject.prioritized_handlers.first
224
+
225
+ # Should not add the handler again.
226
+ subject.with_handler(AlphaHandler) do
227
+ found_handlers = subject.prioritized_handlers.to_a
228
+ assert_equal 1, found_handlers.length
229
+ assert_equal AlphaHandler, subject.prioritized_handlers.first
230
+ end
231
+ end
232
+ assert_equal true, subject.prioritized_handlers.none?
233
+ end
234
+
235
+ should "register the handler with the given priority" do
236
+ priority = 10
237
+ assert_equal true, subject.prioritized_handlers.none?
238
+ subject.with_handler(AlphaHandler, :priority => priority) do
239
+ found_handler = subject.send(:block_handlers)[priority].first
240
+ assert_equal AlphaHandler, found_handler
241
+ end
242
+ assert_equal true, subject.prioritized_handlers.none?
243
+ end
244
+
245
+ should "register the handler with the default priority if no priority given" do
246
+ priority = subject.default_priority
247
+ assert_equal true, subject.prioritized_handlers.none?
248
+ subject.with_handler(AlphaHandler, :priority => priority) do
249
+ found_handler = subject.send(:block_handlers)[priority].first
250
+ assert_equal AlphaHandler, found_handler
251
+ end
252
+ assert_equal true, subject.prioritized_handlers.none?
253
+ end
254
+
255
+ should "remove the handler after yielding" do
256
+ assert_equal true, subject.prioritized_handlers.none?
257
+ subject.with_handler(AlphaHandler) do
258
+ assert_equal AlphaHandler, subject.prioritized_handlers.first
259
+ end
260
+ assert_equal true, subject.prioritized_handlers.none?
261
+ end
262
+
263
+ should "remove the handler even if an error occurs" do
264
+ assert_equal true, subject.prioritized_handlers.none?
265
+ assert_raises(Exception) do
266
+ subject.with_handler(AlphaHandler) do
267
+ assert_equal AlphaHandler, subject.prioritized_handlers.first
268
+ raise Exception
269
+ end
270
+ end
271
+ assert_equal true, subject.prioritized_handlers.none?
272
+ end
273
+ end
274
+
275
+ end
276
+
277
+ def setup_registered_handlers(handler)
278
+ # 5
279
+ assert_equal true, handler.register_handler(AlphaHandler)
280
+ # 2
281
+ assert_equal true, handler.register_handler(AlphaHandler, :priority => 500)
282
+ # 3
283
+ assert_equal true, handler.register_handler(BetaHandler, :priority => 500)
284
+ # 1
285
+ assert_equal true, handler.register_handler(OmegaHandler, :priority => 50)
286
+ # 4
287
+ assert_equal true, handler.register_handler(OmegaHandler, :priority => 500)
288
+
289
+ @expected_handlers = [
290
+ OmegaHandler,
291
+ AlphaHandler,
292
+ BetaHandler,
293
+ OmegaHandler,
294
+ AlphaHandler,
295
+ ]
296
+ end
297
+ end
@@ -0,0 +1,110 @@
1
+ require "test_helper"
2
+
3
+ class RemotelyExceptional::RemoteHandlingTest < RemotelyExceptional::TestCase
4
+ Subject = RemotelyExceptional::RemoteHandling
5
+
6
+ class TestMixer
7
+ include Subject
8
+ end
9
+
10
+ class TestHandler
11
+ include RemotelyExceptional::Handler
12
+ def self.matcher
13
+ lambda { |ex| ex.is_a?(exception_class) }
14
+ end
15
+
16
+ def self.exception_class
17
+ RuntimeError
18
+ end
19
+ end
20
+
21
+ context "class that includes #{Subject.name}" do
22
+
23
+ context "#remotely_exceptional" do
24
+ subject { TestMixer.new }
25
+
26
+ setup do
27
+ @handler = TestHandler
28
+ @instance = TestHandler.new
29
+ @handler.stubs(:new).returns(@instance)
30
+ end
31
+
32
+ should "raise ArgumentError unless a Handler is given" do
33
+ [nil, Class.new, Module.new, :not_a_handler].each do |handler|
34
+ assert_raises(ArgumentError) { subject.remotely_exceptional(handler) }
35
+ end
36
+ end
37
+
38
+ should "yield to the provided block" do
39
+ block_called = false
40
+ subject.remotely_exceptional(@handler) do
41
+ block_called = true
42
+ end
43
+ assert_equal true, block_called
44
+ end
45
+
46
+ context "response codes" do
47
+ should "raise InvalidHandlerResponse if unrecognized response code" do
48
+ @instance.expects(:handle).returns(:not_a_thing)
49
+ exception = assert_raises(RemotelyExceptional::InvalidHandlerResponse) do
50
+ subject.remotely_exceptional(@handler) do
51
+ raise @handler.exception_class
52
+ end
53
+ end
54
+ assert_kind_of @handler.exception_class, exception.original_exception
55
+ end
56
+
57
+ should "retry if retry code is given" do
58
+ @instance.expects(:handle).returns(:retry)
59
+ already_called = false
60
+ retried = false
61
+ subject.remotely_exceptional(@handler) do
62
+ if already_called
63
+ retried = true
64
+ else
65
+ already_called = true
66
+ raise @handler.exception_class
67
+ end
68
+ end
69
+ assert_equal true, retried
70
+ end
71
+
72
+ should "raise if raise code is given" do
73
+ @instance.expects(:handle).returns(:raise)
74
+ assert_raises(@handler.exception_class) do
75
+ subject.remotely_exceptional(@handler) do
76
+ raise @handler.exception_class
77
+ end
78
+ end
79
+ end
80
+
81
+ should "raise given exception if raise code is given with exception" do
82
+ exception_class = ArgumentError
83
+ @instance.expects(:handle).returns([:raise, exception_class])
84
+ assert_raises(exception_class) do
85
+ subject.remotely_exceptional(@handler) do
86
+ raise @handler.exception_class
87
+ end
88
+ end
89
+ end
90
+
91
+ should "continue if continue code is given" do
92
+ @instance.expects(:handle).returns(:continue)
93
+ result = subject.remotely_exceptional(@handler) do
94
+ raise @handler.exception_class
95
+ end
96
+ assert_nil result
97
+ end
98
+
99
+ should "continue and return given value if continue code is given with value" do
100
+ expected_result = 42
101
+ @instance.expects(:handle).returns([:continue, expected_result])
102
+ result = subject.remotely_exceptional(@handler) do
103
+ raise @handler.exception_class
104
+ end
105
+ assert_equal expected_result, result
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,13 @@
1
+ require "test_helper"
2
+
3
+ class RemotelyExceptionalTest < RemotelyExceptional::TestCase
4
+ Subject = RemotelyExceptional
5
+
6
+ subject { Subject }
7
+
8
+ context Subject.name do
9
+ should "be defined" do
10
+ assert defined?(subject), "Expected #{subject.name} to be defined!"
11
+ end
12
+ end
13
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: remotely_exceptional
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Danny Guinther
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-04-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Remote control of exceptions raised in distant contexts.
42
+ email:
43
+ - dannyguinther@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - ".travis.yml"
50
+ - Gemfile
51
+ - Guardfile
52
+ - LICENSE
53
+ - README.md
54
+ - Rakefile
55
+ - lib/remotely_exceptional.rb
56
+ - lib/remotely_exceptional/exceptions.rb
57
+ - lib/remotely_exceptional/handler.rb
58
+ - lib/remotely_exceptional/handlers/prioritized_handler.rb
59
+ - lib/remotely_exceptional/remote_handling.rb
60
+ - lib/remotely_exceptional/version.rb
61
+ - remotely_exceptional.gemspec
62
+ - test/test_helper.rb
63
+ - test/unit/exceptions_test.rb
64
+ - test/unit/handler_test.rb
65
+ - test/unit/handlers/prioritized_handler_test.rb
66
+ - test/unit/remote_handling_test.rb
67
+ - test/unit/remotely_exceptional_test.rb
68
+ homepage: https://github.com/tdg5/remotely_exceptional
69
+ licenses:
70
+ - MIT
71
+ metadata: {}
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ requirements: []
87
+ rubyforge_project:
88
+ rubygems_version: 2.2.2
89
+ signing_key:
90
+ specification_version: 4
91
+ summary: Remote control of exceptions raised in distant contexts.
92
+ test_files:
93
+ - test/test_helper.rb
94
+ - test/unit/exceptions_test.rb
95
+ - test/unit/handler_test.rb
96
+ - test/unit/handlers/prioritized_handler_test.rb
97
+ - test/unit/remote_handling_test.rb
98
+ - test/unit/remotely_exceptional_test.rb
99
+ has_rdoc: