to_robust 1.0.0.pre
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.
- data/LICENSE +20 -0
- data/README.md +39 -0
- data/lib/global/fix.rb +72 -0
- data/lib/global/fix/add_atom.rb +28 -0
- data/lib/global/fix/atom.rb +51 -0
- data/lib/global/fix/init.rb +4 -0
- data/lib/global/fix/remove_atom.rb +16 -0
- data/lib/global/fix/swap_atom.rb +26 -0
- data/lib/global/global.rb +155 -0
- data/lib/global/init.rb +10 -0
- data/lib/global/kernel/argument_error.rb +22 -0
- data/lib/global/kernel/exception.rb +9 -0
- data/lib/global/kernel/init.rb +4 -0
- data/lib/global/kernel/no_method_error.rb +15 -0
- data/lib/global/kernel/zero_division_error.rb +21 -0
- data/lib/global/report.rb +42 -0
- data/lib/global/robust_proc.rb +48 -0
- data/lib/global/strategies/divide_by_zero_error_strategy.rb +277 -0
- data/lib/global/strategies/init.rb +5 -0
- data/lib/global/strategies/no_method_error_strategy.rb +92 -0
- data/lib/global/strategies/wrong_arguments_error_strategy.rb +174 -0
- data/lib/global/strategy.rb +67 -0
- data/lib/local/init.rb +6 -0
- data/lib/local/kernel/init.rb +1 -0
- data/lib/local/kernel/module.rb +43 -0
- data/lib/local/local.rb +43 -0
- data/lib/local/strategies/bignum_division_strategy.rb +11 -0
- data/lib/local/strategies/bignum_modulus_strategy.rb +11 -0
- data/lib/local/strategies/fixnum_coerce_strategy.rb +11 -0
- data/lib/local/strategies/fixnum_division_strategy.rb +12 -0
- data/lib/local/strategies/fixnum_modulus_strategy.rb +12 -0
- data/lib/local/strategies/float_division_strategy.rb +11 -0
- data/lib/local/strategies/float_modulus_strategy.rb +12 -0
- data/lib/local/strategies/init.rb +37 -0
- data/lib/local/strategies/numeric_division_strategy.rb +11 -0
- data/lib/local/strategies/numeric_modulus_strategy.rb +12 -0
- data/lib/local/strategies/soft_binding_strategy.rb +108 -0
- data/lib/local/strategies/swap_method_strategy.rb +32 -0
- data/lib/local/strategy.rb +18 -0
- data/lib/to_robust.rb +4 -0
- data/test/local/float_division.rb +28 -0
- data/test/local/integer_division.rb +28 -0
- data/test/local/method_binding.rb +45 -0
- metadata +109 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2013 Chris Timperley
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
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, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
ruby_to_robust
|
2
|
+
==============
|
3
|
+
|
4
|
+
RubyToRobust allows you to implement soft semantics in your programs so that they
|
5
|
+
may automatically recover from a range of supported exceptions in some sensible manner,
|
6
|
+
allowing the program to continue running.
|
7
|
+
|
8
|
+
The soft semantics provided by this project were originally designed to be exploited by
|
9
|
+
Wallace.rb to provide robustness and support for soft grammars when using Grammatical Evolution.
|
10
|
+
|
11
|
+
Two different soft semantics solutions are provided by RubyToRobust, both based on the
|
12
|
+
Local and Global robustness schemes outlined in Chris Timperley's Masters Thesis.
|
13
|
+
|
14
|
+
* **Global:** The Global robustness module implements soft semantics by intercepting
|
15
|
+
all exceptions thrown by a monitored method and using line information and other information
|
16
|
+
provided by the exception to determine the root of the problem in the method source code
|
17
|
+
which it then proceeds to "repair".
|
18
|
+
* **Local:** Rather than manipulating the source code of the monitored function, the Local
|
19
|
+
robustness measure operates by implementing a series of monkey patches which ensure that
|
20
|
+
a set of supported exceptions are never (or rarely) thrown within the context of the monitored
|
21
|
+
function.
|
22
|
+
|
23
|
+
Please note that the definition of a "repaired method" used by this project is a method which
|
24
|
+
no longer produces errors (which would otherwise crash the program). This does not mean that the
|
25
|
+
program does what its programmer wanted (although sometimes it might).
|
26
|
+
|
27
|
+
The implementation and behaviour of this robustness scheme is very different to the implementation
|
28
|
+
used in Chris Timperley's Master Thesis.
|
29
|
+
* In the original version Global robustness was either enabled or disabled, and was constantly active
|
30
|
+
except in certain classes and modules. The new implementation allows Global robustness to be constrained
|
31
|
+
to a given block or method, so that the program may operate as it otherwise would outside the "protected"
|
32
|
+
block.
|
33
|
+
* Exceptions are treated as soon as they are thrown outside the context of the monitored method.
|
34
|
+
The original Global robustness would allow exceptions to propagate through the program (allowing
|
35
|
+
them to be caught by a parent context) up till the point they would crash Ruby.
|
36
|
+
* Performance is hugely improved! By providing the optional file name and line number parameters to
|
37
|
+
dynamically evaluated procedures there is no need to use external files to extract detailed error information.
|
38
|
+
Additionally, since the soft semantics are constrained to a given block or method, very few method calls
|
39
|
+
have to be wrapped (and therefore slowed down).
|
data/lib/global/fix.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# Instances of this class are used to represent candidate fixes / solutions to given errors that
|
2
|
+
# have occurred during the execution of a method.
|
3
|
+
#
|
4
|
+
# A fix operates on the source code of a method, adding, removing and replacing specific lines of
|
5
|
+
# code (like a Git patch/change) in an attempt to remove the source of the error.
|
6
|
+
#
|
7
|
+
# As mentioned elsewhere in the Global module, strategies may return an ordered sequence of fixes
|
8
|
+
# according to the likelihood that they will solve the problem (or by their performance penalty to
|
9
|
+
# the method), but as this ordering is carried out by the strategies and all ordering information is
|
10
|
+
# implict, candidate fixes proposed by different solutions cannot be compared and ordered.
|
11
|
+
#
|
12
|
+
# The ability to compare arbitrary fixes could be introduced by adding a notion of cost to each fix;
|
13
|
+
# a net quantification of both the performance penalty incurred to the method *after* applying the fix
|
14
|
+
# and the probability that the fix will be a good one (a good fix would have a lower cost than a worse
|
15
|
+
# fix). Candidate fixes could then be processed in ascending order of cost.
|
16
|
+
class ToRobust::Global::Fix
|
17
|
+
|
18
|
+
# Creates a new candidate fix.
|
19
|
+
#
|
20
|
+
# *Parameters:*
|
21
|
+
# * changes, an array of changes that the fix should make to the source code.
|
22
|
+
# * validator, a lambda function that verifies a given error is not the same as the original error.
|
23
|
+
def initialize(changes,validator)
|
24
|
+
@changes = changes
|
25
|
+
@validator = validator
|
26
|
+
end
|
27
|
+
|
28
|
+
# Calculates the source code of the fixed method using the atomic fixes described by this object.
|
29
|
+
#
|
30
|
+
# *Parameters:*
|
31
|
+
# * src, the original source code (as lines) of the affected method.
|
32
|
+
#
|
33
|
+
# *Returns:*
|
34
|
+
# The fixed source code for the method.
|
35
|
+
def fixed_source(src)
|
36
|
+
history = []
|
37
|
+
@changes.each do |change|
|
38
|
+
change.apply!(src, history)
|
39
|
+
history << change
|
40
|
+
end
|
41
|
+
return src
|
42
|
+
end
|
43
|
+
|
44
|
+
# Applies the proposed changes to the source code of the affected method.
|
45
|
+
#
|
46
|
+
# *Parameters:*
|
47
|
+
# * method, the affected method.
|
48
|
+
#
|
49
|
+
# *Returns*
|
50
|
+
# A variant of the affected method modified according to the changes given by this fix.
|
51
|
+
def apply(method)
|
52
|
+
ToRobust::Global::RobustProc.new(method.headers, fixed_source(method.source.dup[1...-1]).join('/\n/)'))
|
53
|
+
end
|
54
|
+
|
55
|
+
# In the event that the method resulting from the fix encounters an error, this method *attempts*
|
56
|
+
# to determine whether the fix has been successful in removing the source of the original error.
|
57
|
+
# A "fixed" method in this sense is not necessarily one which removes all errors from a given method,
|
58
|
+
# rather it removes a particular identifiable error.
|
59
|
+
#
|
60
|
+
# *Parameters:*
|
61
|
+
# * method, the affected method.
|
62
|
+
# * original_error, the original error that was encountered by the method.
|
63
|
+
# * new_error, the error encountered after applying this fix and executing the method again.
|
64
|
+
#
|
65
|
+
# *Returns:*
|
66
|
+
# * True if the fix was successful in removing the cause of the original error, false if not.
|
67
|
+
def successful?(method, original_error, new_error)
|
68
|
+
@validator[method, original_error, new_error]
|
69
|
+
end
|
70
|
+
alias_method :validate, :successful?
|
71
|
+
|
72
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# A add atom is used to insert a sequence of lines at a given position in the affected source code.
|
2
|
+
class ToRobust::Global::Fix::AddAtom < ToRobust::Global::Fix::Atom
|
3
|
+
|
4
|
+
# Constructs a new add atom.
|
5
|
+
#
|
6
|
+
# *Parameters:*
|
7
|
+
# * line_no, the line in the source code which the atom should operate on.
|
8
|
+
# * additions, the sequence of lines to insert at the given line number (as an ordered array).
|
9
|
+
def initialize(line_no, additions)
|
10
|
+
super(line_no)
|
11
|
+
@additions = additions
|
12
|
+
end
|
13
|
+
|
14
|
+
# Calculates and returns the number of lines added by the atom.
|
15
|
+
def lines_added
|
16
|
+
@additions.length
|
17
|
+
end
|
18
|
+
|
19
|
+
# Inserts a sequence of lines at specific line in the affected source code.
|
20
|
+
#
|
21
|
+
# *Parameters:*
|
22
|
+
# * source, the (partially fixed) affected source.
|
23
|
+
# * changes, an array of the atomic changes already applied to this source (in order of application).
|
24
|
+
def apply!(source, changes)
|
25
|
+
source.insert(adjusted_line_no(changes), *@additions)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# A fix atom is used to store and represent a single "atomic" change made by a candidate fix which
|
2
|
+
# may be represented as a collection of such atomic fixes.
|
3
|
+
class ToRobust::Global::Fix::Atom
|
4
|
+
|
5
|
+
# The line which this atomic fix changes.
|
6
|
+
attr_reader :line_no
|
7
|
+
|
8
|
+
# Constructs a new fix atom.
|
9
|
+
#
|
10
|
+
# *Parameters:*
|
11
|
+
# * line_no, the line in the source code which the atom should operate on.
|
12
|
+
def initialize(line_no)
|
13
|
+
@line_no = line_no
|
14
|
+
end
|
15
|
+
|
16
|
+
# Calculates the number of lines that will be added by this fix.
|
17
|
+
def lines_added
|
18
|
+
raise NotImplementedError, 'No "lines_added" method was implemented by this Atom.'
|
19
|
+
end
|
20
|
+
|
21
|
+
# Calculate the line number in the adjusted source code that corresponds to the specified
|
22
|
+
# line in the body of the original source code.
|
23
|
+
#
|
24
|
+
# *Parameters:*
|
25
|
+
# * changes, an array of the atomic changes already applied to this source.
|
26
|
+
def adjusted_line_no(changes)
|
27
|
+
|
28
|
+
adj_n = @line_no
|
29
|
+
changes.each do |c|
|
30
|
+
if c.line_no < @line_no and lines_added < 0
|
31
|
+
adj_n += c.lines_added
|
32
|
+
elsif c.line_no >= line_no and lines_added > 0
|
33
|
+
adj_n += c.lines_added
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Subtract one to return the line number in the body (header takes up the first line).
|
38
|
+
return adj_n - 1
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
# Destructively applies the change described by this atomic fix to the given source code.
|
43
|
+
#
|
44
|
+
# *Parameters:*
|
45
|
+
# * source, the source code of the affected method (represented as a sequence of lines).
|
46
|
+
# * changes, an array of the atomic changes already applied to this source (in order of application).
|
47
|
+
def apply!(source, changes)
|
48
|
+
raise NotImplementedError, 'No "apply!" method was implemented by this Atom.'
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# A remove atom is used to remove a line from the affected source code.
|
2
|
+
class ToRobust::Global::Fix::RemoveAtom < ToRobust::Global::Fix::Atom
|
3
|
+
|
4
|
+
# Removes a single line from the source code.
|
5
|
+
def lines_added; -1; end
|
6
|
+
|
7
|
+
# Removes a specific line from the affected source code.
|
8
|
+
#
|
9
|
+
# *Parameters:*
|
10
|
+
# * source, the (partially fixed) affected source.
|
11
|
+
# * changes, an array of the atomic changes already applied to this source (in order of application).
|
12
|
+
def apply!(source, changes)
|
13
|
+
source.delete_at(adjusted_line_no(changes))
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# A remove atom is used to swap a line from the affected source code with another.
|
2
|
+
class ToRobust::Global::Fix::SwapAtom < ToRobust::Global::Fix::Atom
|
3
|
+
|
4
|
+
# Constructs a new swap atom.
|
5
|
+
#
|
6
|
+
# *Parameters:*
|
7
|
+
# * line_no, the line in the source code which the atom should operate on.
|
8
|
+
# * replacement, the contents of the replacement line.
|
9
|
+
def initialize(line_no, replacement)
|
10
|
+
super(line_no)
|
11
|
+
@replacement = replacement
|
12
|
+
end
|
13
|
+
|
14
|
+
# No lines are added.
|
15
|
+
def lines_added; 0; end
|
16
|
+
|
17
|
+
# Swaps the contents at specific line with an altered form stored by this object.
|
18
|
+
#
|
19
|
+
# *Parameters:*
|
20
|
+
# * source, the (partially fixed) affected source.
|
21
|
+
# * changes, an array of the atomic changes already applied to this source (in order of application).
|
22
|
+
def apply!(source, changes)
|
23
|
+
source[adjusted_line_no(changes)] = @replacement
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# The Global robustness module implements a simplified (and faster) variant of the global
|
2
|
+
# robustness measure outlined in Chris Timperley's Master's Thesis.
|
3
|
+
#
|
4
|
+
# Methods executed under global robustness protection are executed within an exception
|
5
|
+
# catching block which intercept any errors which cause the method to fail
|
6
|
+
#
|
7
|
+
# For now this measure only works with simple methods that do not change the state of
|
8
|
+
# objects or interfere with the global state.
|
9
|
+
#
|
10
|
+
# Author: Chris Timperley
|
11
|
+
module ToRobust::Global
|
12
|
+
|
13
|
+
@strategies = []
|
14
|
+
@max_attempts = 10
|
15
|
+
@save_repairs = true
|
16
|
+
|
17
|
+
class << self
|
18
|
+
|
19
|
+
# A flag to control whether repairs to a method should be permanent (true) or if they
|
20
|
+
# should last only for a given call (false).
|
21
|
+
# True by default.
|
22
|
+
attr_accessor :save_repairs
|
23
|
+
|
24
|
+
# The maximum number of (different) errors that will be tolerated before
|
25
|
+
# all attempts to repair the method are ceased.
|
26
|
+
# 10 by default.
|
27
|
+
attr_accessor :max_attempts
|
28
|
+
|
29
|
+
# Allow strategies to be added and removed via:
|
30
|
+
# * Global.strategies << Strategy.new
|
31
|
+
# * Global.strategies.remove(strategy)
|
32
|
+
attr_reader :strategies
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
# Executes a given method (or proc) under global robustness protection.
|
37
|
+
#
|
38
|
+
# *Parameters:*
|
39
|
+
# * method, the (robust proc) method to execute using global robustness protection.
|
40
|
+
# * params, the parameters to the method call.
|
41
|
+
#
|
42
|
+
# *Returns:*
|
43
|
+
# The result of the method call.
|
44
|
+
def self.execute(method, params)
|
45
|
+
|
46
|
+
# Attempt to execute the method. If an error occurs then attempt to repair the method
|
47
|
+
# using the error information.
|
48
|
+
attempts = 0
|
49
|
+
begin
|
50
|
+
return method.call(*params.clone)
|
51
|
+
rescue => original_error
|
52
|
+
report = repair(method, params, original_error)
|
53
|
+
attempts += 1
|
54
|
+
end
|
55
|
+
|
56
|
+
# Once repaired attempt to call the method again, repeating the cycle,
|
57
|
+
# until either the method successfully returns a result or a maximum number of errors occur.
|
58
|
+
until attempts >= @max_attempts or report.complete?
|
59
|
+
attempts += 1
|
60
|
+
report = repair(report.fixed_method, params, report.error)
|
61
|
+
end
|
62
|
+
|
63
|
+
# If the repair limit has been hit then return the original error and make no permanent
|
64
|
+
# changes to the supplied method (or should we keep the changes?).
|
65
|
+
raise original_error if report.partial?
|
66
|
+
|
67
|
+
# If the method was completely repaired then swap the original method with the fixed form
|
68
|
+
# (so long as that functionality is enabled) and return the result of the method call.
|
69
|
+
method.become(report.fixed_method) if @save_repairs
|
70
|
+
return report.result
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
# Calculates all the possible candidate fixes to a given error within a provided method.
|
75
|
+
# Passes the details of the error along with the method to each associated error handling
|
76
|
+
# strategy and queries all possible candidate solutions.
|
77
|
+
#
|
78
|
+
# *TODO:*
|
79
|
+
# At the moment each strategy may return an ordered set of candidate fixes to the problem,
|
80
|
+
# but this ordering information is implicit (and usually not universally comparable) so there
|
81
|
+
# can be no ordering of candidate fixes from ALL given strategies (i.e. strategy B may produce
|
82
|
+
# a strategy that is more likely to solve the error than the best candidate given by strategy A).
|
83
|
+
#
|
84
|
+
# *Parameters:*
|
85
|
+
# * method, the method to be fixed.
|
86
|
+
# * error, the error to be addressed.
|
87
|
+
#
|
88
|
+
# *Returns:*
|
89
|
+
# An array of candidate fixes to the error.
|
90
|
+
def self.generate_candidates(method, error)
|
91
|
+
@strategies.map { |s| s.generate_candidates(method, error) }.flatten
|
92
|
+
end
|
93
|
+
|
94
|
+
# Repairs a given (statically defined) method using the details of an encountered error.
|
95
|
+
#
|
96
|
+
# Special care must be taken to define repair. In this context, a repaired method
|
97
|
+
# is one which does not suffer from the same error that it did originally. This means
|
98
|
+
# that the repaired form of the provided method may still contain errors.
|
99
|
+
#
|
100
|
+
# This definition of repair is necessary to fix methods which contain more than a single
|
101
|
+
# error.
|
102
|
+
#
|
103
|
+
# *Parameters:*
|
104
|
+
# * method, the broken method to be repaired.
|
105
|
+
# * params, the parameters to the original method call.
|
106
|
+
# * error, the error that occurred when the method was called.
|
107
|
+
#
|
108
|
+
# *Returns:*
|
109
|
+
# A repaired form of the method.
|
110
|
+
def self.repair(method, params, error)
|
111
|
+
|
112
|
+
# Produce a series of candidate fixes to the method.
|
113
|
+
candidates = generate_candidates(method, error)
|
114
|
+
|
115
|
+
# Attempt each of the candidate fixes until the error is prevented or there are no
|
116
|
+
# further candidates to try.
|
117
|
+
until candidates.empty?
|
118
|
+
|
119
|
+
fix = candidates.shift
|
120
|
+
fixed_method = fix.apply(method)
|
121
|
+
|
122
|
+
begin
|
123
|
+
result = fixed_method.call(*params.clone)
|
124
|
+
return Report.new(fixed_method, result: result)
|
125
|
+
rescue => fix_error
|
126
|
+
return Report.new(fixed_method, error: fix_error) if fix.successful?(fixed_method, error, fix_error)
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
# If all attempts to repair the method against the given error fail then re-throw
|
132
|
+
# the exception.
|
133
|
+
raise error
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
# Executes a given block whilst preventing a ZeroDivisionError occurring.
|
138
|
+
# If a zero division error does occur during the execution of the block, this safety
|
139
|
+
# wrapper will catch the exception and will return a zero instead, allowing the program
|
140
|
+
# to continue.
|
141
|
+
#
|
142
|
+
# *Parameters:*
|
143
|
+
# * &block, the block to execute under ZeroDivisionError protection.
|
144
|
+
#
|
145
|
+
# *Returns:*
|
146
|
+
# The result of the block execution, or zero if the block execution yields a ZeroDivisionError.
|
147
|
+
def self.prevent_dbz(&block)
|
148
|
+
begin
|
149
|
+
return block.call
|
150
|
+
rescue ZeroDivisionError
|
151
|
+
return 0
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
data/lib/global/init.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
module ToRobust; end
|
2
|
+
|
3
|
+
require_relative 'kernel/init.rb'
|
4
|
+
require_relative 'global.rb'
|
5
|
+
require_relative 'robust_proc.rb'
|
6
|
+
require_relative 'strategy.rb'
|
7
|
+
require_relative 'strategies/init.rb'
|
8
|
+
require_relative 'fix.rb'
|
9
|
+
require_relative 'fix/init.rb'
|
10
|
+
require_relative 'report.rb'
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Augments the functionality of ArgumentError instances so that the affected method and expected number
|
2
|
+
# of arguments can be quickly inspected. Also adjusts the line number calculation so that the correct
|
3
|
+
# line number is returned for the error.
|
4
|
+
class ArgumentError
|
5
|
+
|
6
|
+
# Returns the name of the method which was provided with poorly formed arguments.
|
7
|
+
def affected_method
|
8
|
+
backtrace[0].match(/`(([a-zA-Z]|_)+)'/)[0][1...-1]
|
9
|
+
end
|
10
|
+
|
11
|
+
# Returns the number of arguments that were expected by this method.
|
12
|
+
def args_expected
|
13
|
+
message.match(/\s(\d+)\)/)[0][1...-1].to_i
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns the line number that the method call was made from.
|
17
|
+
# The line that the method call was made is given by the second line of the backtrace.
|
18
|
+
def line_no
|
19
|
+
backtrace[1][/:(\d+):/][1...-1].to_i
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|