Permalink
Browse files

Create nested_exceptions gem

  • Loading branch information...
0 parents commit a735b3fe7dde386e9c0d145f29a40fb37c6a718c Darrick Wiebe committed Sep 21, 2011
4 .gitignore
@@ -0,0 +1,4 @@
+*.gem
+.bundle
+Gemfile.lock
+pkg/*
0 .rspec
No changes.
4 Gemfile
@@ -0,0 +1,4 @@
+source "http://rubygems.org"
+
+# Specify your gem's dependencies in nested_exceptions.gemspec
+gemspec
17 Rakefile
@@ -0,0 +1,17 @@
+require "bundler/gem_tasks"
+
+require 'rspec/core/rake_task'
+
+RSpec::Core::RakeTask.new(:spec) do |spec|
+ spec.pattern = FileList['spec/**/*_spec.rb']
+end
+
+RSpec::Core::RakeTask.new(:rcov) do |spec|
+ spec.pattern = 'spec/**/*_spec.rb'
+ spec.ruby_opts = '--debug'
+ spec.skip_bundler = true
+ spec.rcov = true
+ spec.rcov_opts = %w{--exclude generator_internal,jsignal_internal,gems\/,spec\/}
+end
+
+task :default => :spec
16 bin/autospec
@@ -0,0 +1,16 @@
+#!/usr/bin/env jruby
+#
+# This file was generated by Bundler.
+#
+# The application 'autospec' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+require 'pathname'
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
+ Pathname.new(__FILE__).realpath)
+
+require 'rubygems'
+require 'bundler/setup'
+
+load Gem.bin_path('rspec-core', 'autospec')
16 bin/autotest
@@ -0,0 +1,16 @@
+#!/usr/bin/env jruby
+#
+# This file was generated by Bundler.
+#
+# The application 'autotest' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+require 'pathname'
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
+ Pathname.new(__FILE__).realpath)
+
+require 'rubygems'
+require 'bundler/setup'
+
+load Gem.bin_path('ZenTest', 'autotest')
16 bin/htmldiff
@@ -0,0 +1,16 @@
+#!/usr/bin/env jruby
+#
+# This file was generated by Bundler.
+#
+# The application 'htmldiff' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+require 'pathname'
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
+ Pathname.new(__FILE__).realpath)
+
+require 'rubygems'
+require 'bundler/setup'
+
+load Gem.bin_path('diff-lcs', 'htmldiff')
16 bin/ldiff
@@ -0,0 +1,16 @@
+#!/usr/bin/env jruby
+#
+# This file was generated by Bundler.
+#
+# The application 'ldiff' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+require 'pathname'
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
+ Pathname.new(__FILE__).realpath)
+
+require 'rubygems'
+require 'bundler/setup'
+
+load Gem.bin_path('diff-lcs', 'ldiff')
16 bin/multigem
@@ -0,0 +1,16 @@
+#!/usr/bin/env jruby
+#
+# This file was generated by Bundler.
+#
+# The application 'multigem' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+require 'pathname'
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
+ Pathname.new(__FILE__).realpath)
+
+require 'rubygems'
+require 'bundler/setup'
+
+load Gem.bin_path('ZenTest', 'multigem')
16 bin/multiruby
@@ -0,0 +1,16 @@
+#!/usr/bin/env jruby
+#
+# This file was generated by Bundler.
+#
+# The application 'multiruby' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+require 'pathname'
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
+ Pathname.new(__FILE__).realpath)
+
+require 'rubygems'
+require 'bundler/setup'
+
+load Gem.bin_path('ZenTest', 'multiruby')
16 bin/multiruby_setup
@@ -0,0 +1,16 @@
+#!/usr/bin/env jruby
+#
+# This file was generated by Bundler.
+#
+# The application 'multiruby_setup' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+require 'pathname'
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
+ Pathname.new(__FILE__).realpath)
+
+require 'rubygems'
+require 'bundler/setup'
+
+load Gem.bin_path('ZenTest', 'multiruby_setup')
16 bin/rspec
@@ -0,0 +1,16 @@
+#!/usr/bin/env jruby
+#
+# This file was generated by Bundler.
+#
+# The application 'rspec' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+require 'pathname'
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
+ Pathname.new(__FILE__).realpath)
+
+require 'rubygems'
+require 'bundler/setup'
+
+load Gem.bin_path('rspec-core', 'rspec')
16 bin/unit_diff
@@ -0,0 +1,16 @@
+#!/usr/bin/env jruby
+#
+# This file was generated by Bundler.
+#
+# The application 'unit_diff' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+require 'pathname'
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
+ Pathname.new(__FILE__).realpath)
+
+require 'rubygems'
+require 'bundler/setup'
+
+load Gem.bin_path('ZenTest', 'unit_diff')
16 bin/zentest
@@ -0,0 +1,16 @@
+#!/usr/bin/env jruby
+#
+# This file was generated by Bundler.
+#
+# The application 'zentest' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+require 'pathname'
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
+ Pathname.new(__FILE__).realpath)
+
+require 'rubygems'
+require 'bundler/setup'
+
+load Gem.bin_path('ZenTest', 'zentest')
39 lib/nested_exceptions.rb
@@ -0,0 +1,39 @@
+require "nested_exceptions/version"
+require "nested_exceptions/define"
+
+# Just include this module in your custom exception class
+module NestedExceptions
+ attr_reader :cause
+
+ def initialize(message = nil, cause = $!)
+ super(message)
+ @cause = cause
+ end
+
+ def backtrace
+ return @processed_backtrace if defined? @processed_backtrace
+ @processed_backtrace = super
+ if cause
+ cause.backtrace.reverse.each do |line|
+ if @processed_backtrace.last == line
+ @processed_backtrace.pop
+ else
+ break
+ end
+ end
+ @processed_backtrace << "cause: #{cause.class.name}: #{cause}"
+ @processed_backtrace.concat cause.backtrace
+ end
+ @processed_backtrace
+ end
+
+ def root_cause
+ rc = e = self
+ while e
+ rc = e
+ break unless e.respond_to? :cause
+ e = e.cause
+ end
+ rc
+ end
+end
23 lib/nested_exceptions/define.rb
@@ -0,0 +1,23 @@
+module NestedExceptions
+ class << self
+ # Define a set of standard exception classes as recommended in
+ # http://exceptionalruby.com/
+ #
+ # I actually recommend that you just define these manually, but this may be
+ # a handy shortcut in smaller projects.
+ def define_standard_exception_classes_in(target_module)
+ definitions = %{
+ class Error < StandardError; include NestedExceptions; end
+ class UserError < Error; end
+ class LogicError < Error; end
+ class ClientError < LogicError; end
+ class InternalError < LogicError; end
+ class TransientError < Error; end
+ }
+ target_module.module_eval definitions
+ [:Error, :UserError, :LogicError, :ClientError, :InternalError, :TransientError].map do |name|
+ target_module.const_get name
+ end
+ end
+ end
+end
7 lib/nested_exceptions/global.rb
@@ -0,0 +1,7 @@
+require 'nested_exceptions'
+
+NestedExceptions::GLOBAL = true
+
+[NoMemoryError, ScriptError, SignalException, StandardError, SystemExit].each do |klass|
+ klass.send :include, NestedExceptions
+end
11 lib/nested_exceptions/version.rb
@@ -0,0 +1,11 @@
+module NestedExceptions
+ VERSION = "1.0.0"
+
+ def self.const_missing(name)
+ if name == 'GLOBAL' or name == :GLOBAL
+ NestedExceptions.const_set(name, false)
+ else
+ super
+ end
+ end
+end
22 nested_exceptions.gemspec
@@ -0,0 +1,22 @@
+# -*- encoding: utf-8 -*-
+$:.push File.expand_path("../lib", __FILE__)
+require "nested_exceptions/version"
+
+Gem::Specification.new do |s|
+ s.name = "nested_exceptions"
+ s.version = NestedExceptions::VERSION
+ s.authors = ["Darrick Wiebe"]
+ s.email = ["dw@xnlogic.com"]
+ s.homepage = ""
+ s.summary = %q{Support nested exceptions in all rubies}
+ s.description = %q{Based on ideas in http://exceptionalruby.com/ and the nestegg gem which does not support JRuby.}
+
+ s.rubyforge_project = "nested_exceptions"
+
+ s.files = `git ls-files`.split("\n")
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
+ s.require_paths = ["lib"]
+
+ s.add_development_dependency "rspec"
+ s.add_development_dependency "autotest"
+end
87 spec/nested_exceptions_spec.rb
@@ -0,0 +1,87 @@
+require 'spec_helper'
+
+module ErrorSpec
+
+ NestedExceptions.define_standard_exception_classes_in ErrorSpec
+
+ class Example
+ def bug
+ raise 'bug'
+ end
+
+ def nested_bug
+ bug
+ end
+
+ def problem
+ nested_bug
+ rescue
+ raise InternalError, 'problem'
+ end
+
+ def nested_problem
+ problem
+ end
+
+ def double_bug
+ nested_problem
+ rescue
+ raise 'oops'
+ end
+ end
+end
+
+describe NestedExceptions do
+ let(:ex) { ErrorSpec::Example.new }
+
+ it 'should create a stack trace correctly' do
+ stack = raise rescue $!.backtrace
+ stack.first.should == "#{__FILE__}:#{__LINE__ - 1}:in `(root)'"
+ end
+
+ describe 'normal StandardError' do
+ subject do
+ @stack = raise rescue $!.backtrace; ex.bug rescue $!
+ end
+ its(:message) { should == 'bug' }
+ its(:backtrace) { should == ["#{__FILE__}:9:in `bug'"] + @stack }
+ end
+
+ describe 'nested InternalError' do
+ subject do
+ @stack = raise rescue $!.backtrace; ex.problem rescue $!
+ end
+ its(:message) { should == 'problem' }
+ its('root_cause.message') { should == 'bug' }
+ its(:backtrace) do
+ should == [
+ "#{__FILE__}:19:in `problem'",
+ "cause: RuntimeError: bug",
+ "#{__FILE__}:9:in `bug'",
+ "#{__FILE__}:13:in `nested_bug'",
+ "#{__FILE__}:17:in `problem'"
+ ] + @stack
+ end
+ end
+
+ describe 'double nested StandardError' do
+ subject do
+ @stack = raise rescue $!.backtrace; ex.double_bug rescue $!
+ end
+ its(:message) { should == 'oops' }
+ its('root_cause.message') { should == 'bug' }
+ its(:backtrace) do
+ should == [
+ "#{__FILE__}:29:in `double_bug'",
+ "cause: ErrorSpec::InternalError: problem",
+ "#{__FILE__}:19:in `problem'",
+ "cause: RuntimeError: bug",
+ "#{__FILE__}:9:in `bug'",
+ "#{__FILE__}:13:in `nested_bug'",
+ "#{__FILE__}:17:in `problem'",
+ "#{__FILE__}:23:in `nested_problem'",
+ "#{__FILE__}:27:in `double_bug'",
+ ] + @stack
+ end
+ end
+end
2 spec/spec_helper.rb
@@ -0,0 +1,2 @@
+require 'rspec'
+require 'nested_exceptions/global'

0 comments on commit a735b3f

Please sign in to comment.