serializable_proc 0.1.0

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.
@@ -0,0 +1,169 @@
1
+ require 'rubygems'
2
+ require 'forwardable'
3
+ require 'ruby2ruby'
4
+ require 'serializable_proc/marshalable'
5
+ require 'serializable_proc/parsers'
6
+ require 'serializable_proc/binding'
7
+ require 'serializable_proc/sandboxer'
8
+
9
+ begin
10
+ require 'parse_tree'
11
+ require 'parse_tree_extensions'
12
+ rescue LoadError
13
+ require 'ruby_parser'
14
+ end
15
+
16
+ ##
17
+ # SerializableProc differs from the vanilla Proc in 2 ways:
18
+ #
19
+ # #1. Isolated variables
20
+ #
21
+ # Upon initializing, all variables (local, instance, class & global) within its context
22
+ # are extracted from the proc's binding, and are isolated from changes outside the
23
+ # proc's scope, thus, achieving a snapshot effect.
24
+ #
25
+ # require 'rubygems'
26
+ # require 'serializable_proc'
27
+ #
28
+ # x, @x, @@x, $x = 'lx', 'ix', 'cx', 'gx'
29
+ #
30
+ # s_proc = SerializableProc.new { [x, @x, @@x, $x].join(', ') }
31
+ # v_proc = Proc.new { [x, @x, @@x, $x].join(', ') }
32
+ #
33
+ # x, @x, @@x, $x = 'ly', 'iy', 'cy', 'gy'
34
+ #
35
+ # s_proc.call # >> "lx, ix, cx, gx"
36
+ # v_proc.call # >> "ly, iy, cy, gy"
37
+ #
38
+ # #2. Marshallable
39
+ #
40
+ # No throwing of TypeError when marshalling a SerializableProc:
41
+ #
42
+ # Marshal.load(Marshal.dump(s_proc)).call # >> "lx, ix, cx, gx"
43
+ # Marshal.load(Marshal.dump(v_proc)).call # >> TypeError (cannot dump Proc)
44
+ #
45
+ class SerializableProc
46
+
47
+ include Marshalable
48
+ marshal_attrs :file, :line, :code, :arity, :binding
49
+
50
+ ##
51
+ # Creates a new instance of SerializableProc by passing in a code block, in the process,
52
+ # all referenced variables (local, instance, class & global) within the block are
53
+ # extracted and isolated from the current context.
54
+ #
55
+ # SerializableProc.new {|...| block }
56
+ # x = lambda { ... }; SerializableProc.new(&x)
57
+ # y = proc { ... }; SerializableProc.new(&y)
58
+ # z = Proc.new { ... }; SerializableProc.new(&z)
59
+ #
60
+ # The following will only work if u have ParseTree (not available for 1.9.* & JRuby)
61
+ # installed:
62
+ #
63
+ # def action(&block) ; SerializableProc.new(&block) ; end
64
+ # action { ... }
65
+ #
66
+ def initialize(&block)
67
+ file, line = /^#<Proc:0x[0-9A-Fa-f]+@(.+):(\d+).*?>$/.match(block.inspect)[1..2]
68
+ @file, @line, @arity = File.expand_path(file), line.to_i, block.arity
69
+ @code, sexp = Parsers::PT.process(block) || Parsers::RP.process(self.class, @file, @line)
70
+ @binding = Binding.new(block.binding, sexp)
71
+ end
72
+
73
+ ##
74
+ # Returns true if +other+ is exactly the same instance, or if +other+ has the same string
75
+ # content.
76
+ #
77
+ # x = SerializableProc.new { puts 'awesome' }
78
+ # y = SerializableProc.new { puts 'wonderful' }
79
+ # z = SerializableProc.new { puts 'awesome' }
80
+ #
81
+ # x == x # >> true
82
+ # x == y # >> false
83
+ # x == z # >> true
84
+ #
85
+ def ==(other)
86
+ other.object_id == object_id or
87
+ other.is_a?(self.class) && other.to_s == to_s
88
+ end
89
+
90
+ ##
91
+ # Returns a plain vanilla proc that works just like other instances of Proc, the
92
+ # only difference is that the binding of variables is the same as the serializable
93
+ # proc, which is isolated.
94
+ #
95
+ # x, @x, @@x, $x = 'lx', 'ix', 'cx', 'gx'
96
+ # s_proc = SerializableProc.new { [x, @x, @@x, $x].join(', ') }
97
+ # x, @x, @@x, $x = 'ly', 'iy', 'cy', 'gy'
98
+ # s_proc.to_proc.call # >> 'lx, ix, cx, gx'
99
+ #
100
+ # Just like any object that responds to #to_proc, you can do the following as well:
101
+ #
102
+ # def action(&block) ; yield ; end
103
+ # action(&s_proc) # >> 'lx, ix, cx, gx'
104
+ #
105
+ def to_proc
106
+ @proc ||= eval(@code[:runnable], @binding.eval!, @file, @line)
107
+ end
108
+
109
+ ##
110
+ # Returns a string representation of itself, which is in fact the code enclosed within
111
+ # the initializing block.
112
+ #
113
+ # SerializableProc.new { [x, @x, @@x, $x].join(', ') }.to_s
114
+ # # >> lambda { [x, @x, @@x, $x].join(', ') }
115
+ #
116
+ # By specifying +debug+ as true, the true runnable code is returned, the only difference
117
+ # from the above is that the variables within has been renamed (in order to provide for
118
+ # variables isolation):
119
+ #
120
+ # SerializableProc.new { [x, @x, @@x, $x].join(', ') }.to_s(true)
121
+ # # >> lambda { [lvar_x, ivar_x, cvar_x, gvar_x].join(', ') }
122
+ #
123
+ # The following renaming rules apply:
124
+ # * local variable -> prefixed with 'lvar_',
125
+ # * instance variable -> replaced '@' with 'ivar_'
126
+ # * class variable -> replaced '@@' with 'cvar_'
127
+ # * global variable -> replaced '$ with 'gvar_'
128
+ #
129
+ def to_s(debug = false)
130
+ @code[debug ? :runnable : :extracted]
131
+ end
132
+
133
+ ##
134
+ # Returns the number of arguments accepted when running #call. This is extracted directly
135
+ # from the initializing code block, & is only as accurate as Proc#arity.
136
+ #
137
+ # Note that at the time of this writing, running on 1.8.* yields different result from
138
+ # that of 1.9.*:
139
+ #
140
+ # lambda { }.arity # 1.8.* (-1) / 1.9.* (0) (?!)
141
+ # lambda {|x| }.arity # 1.8.* (1) / 1.9.* (1)
142
+ # lambda {|x,y| }.arity # 1.8.* (2) / 1.9.* (2)
143
+ # lambda {|*x| }.arity # 1.8.* (-1) / 1.9.* (-1)
144
+ # lambda {|x, *y| }.arity # 1.8.* (-2) / 1.9.* (-2)
145
+ # lambda {|(x,y)| }.arity # 1.8.* (1) / 1.9.* (1)
146
+ #
147
+ def arity
148
+ @arity
149
+ end
150
+
151
+ ##
152
+ # Just like the vanilla proc, invokes it, setting params as specified. Since the code
153
+ # representation of a SerializableProc is a lambda, expect lambda-like behaviour when
154
+ # wrong number of params are passed in.
155
+ #
156
+ # SerializableProc.new{|i| (['hello'] * i).join(' ') }.call(2)
157
+ # # >> 'hello hello'
158
+ #
159
+ def call(*params)
160
+ to_proc.call(*params)
161
+ end
162
+
163
+ alias_method :[], :call
164
+
165
+ def binding #:nodoc:
166
+ raise NotImplementedError
167
+ end
168
+
169
+ end
@@ -0,0 +1,99 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe 'Extracting bound variables' do
4
+
5
+ class << self
6
+ def m1 ; 'm1' ; end
7
+ def m2(x) ; 'm2(%s)' % x ; end
8
+ end
9
+
10
+ describe '>> extracting local variables' do
11
+
12
+ extend SerializableProc::Spec::Helpers
13
+
14
+ should "handle outer-scoped ones" do
15
+ x, y = 'awe', 'some'
16
+ should_have_expected_binding \
17
+ SerializableProc.new{ x + y }, {:lvar_x => x, :lvar_y => y}
18
+ end
19
+
20
+ should "handle inner-scoped ones" do
21
+ x, y = 'awe', 'some'
22
+ should_have_expected_binding \
23
+ SerializableProc.new{|x| z = 'wonder' ; %w{a b}.each{|y| puts z, x, y } },
24
+ {:lvar_x => x, :lvar_y => y, :lvar_z => nil}
25
+ end
26
+
27
+ # NOTE: Errors checking are found under ./initializing_errors_spec.rb
28
+
29
+ end
30
+
31
+ describe '>> extracting instance variables' do
32
+
33
+ extend SerializableProc::Spec::Helpers
34
+
35
+ should 'handle outer-scoped ones' do
36
+ @x, @y = 'awe', 'some'
37
+ should_have_expected_binding \
38
+ SerializableProc.new{ @x + @y }, {:ivar_x => @x, :ivar_y => @y}
39
+ end
40
+
41
+ should "handle inner-scoped ones" do
42
+ @x, @y = 'awe', 'some'
43
+ should_have_expected_binding \
44
+ SerializableProc.new{ @z = 'wonder' ; %w{a b}.each{ puts @z, @x, @y } },
45
+ {:ivar_x => @x, :ivar_y => @y, :ivar_z => nil}
46
+ end
47
+
48
+ # NOTE: Errors checking are found under ./initializing_errors_spec.rb
49
+
50
+ end
51
+
52
+ describe '>> extracting class variables' do
53
+
54
+ extend SerializableProc::Spec::Helpers
55
+
56
+ should 'handle outer-scoped ones' do
57
+ @@x, @@y = 'awe', 'some'
58
+ should_have_expected_binding \
59
+ SerializableProc.new{ @@x + @@y }, {:cvar_x => @@x, :cvar_y => @@y}
60
+ end
61
+
62
+ should "handle inner-scoped ones" do
63
+ @@x, @@y = 'awe', 'some'
64
+ should_have_expected_binding \
65
+ SerializableProc.new{ @@z = 'wonder' ; %w{a b}.each{ puts @@z, @@x, @@y } },
66
+ {:cvar_x => @@x, :cvar_y => @@y, :cvar_z => nil}
67
+ end
68
+
69
+ # NOTE: Errors checking are found under ./initializing_errors_spec.rb
70
+
71
+ end
72
+
73
+ describe '>> extracting global variables' do
74
+
75
+ extend SerializableProc::Spec::Helpers
76
+
77
+ should 'handle outer-scoped ones' do
78
+ $x, $y = 'awe', 'some'
79
+ should_have_expected_binding SerializableProc.new{ $x + $y }, {:gvar_x => $x, :gvar_y => $y}
80
+ end
81
+
82
+ should "handle inner-scoped ones" do
83
+ $x, $y = 'awe', 'some'
84
+ should_have_expected_binding \
85
+ SerializableProc.new{ $z = 'wonder' ; %w{a b}.each{ puts $z, $x, $y } },
86
+ {:gvar_x => $x, :gvar_y => $y, :gvar_z => nil}
87
+ end
88
+
89
+ # NOTE: Errors checking are found under ./initializing_errors_spec.rb
90
+
91
+ end
92
+
93
+ describe '>> extracting method returns' do
94
+ should "not handle" do
95
+ SerializableProc.new { m1 + m2(3) }.binding_dump.should.be.empty
96
+ end
97
+ end
98
+
99
+ end
@@ -0,0 +1,95 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe 'Initializing errors' do
4
+
5
+ unless $parse_tree_installed
6
+ describe '>> SerializableProc::CannotAnalyseCodeError' do
7
+
8
+ extend SerializableProc::Spec::Helpers
9
+
10
+ should "raise if line has more than 1 'SerializableProc.new'" do
11
+ lambda {
12
+ SerializableProc.new {} ; SerializableProc.new { |arg| %w{a b}.map{|x| puts x } }
13
+ }.should.be raising_cannot_analyse_error("'SerializableProc.new'")
14
+ end
15
+
16
+ should "raise if line has more than 1 'Proc.new'" do
17
+ lambda {
18
+ p1 = Proc.new {} ; p2 = Proc.new { |arg| %w{a b}.map{|x| puts x } }
19
+ SerializableProc.new(&p2)
20
+ }.should.be raising_cannot_analyse_error("'lambda'/'proc'/'Proc.new'")
21
+ end
22
+
23
+ should "raise if line has more than 1 'lambda'" do
24
+ lambda {
25
+ p1 = lambda {} ; p2 = lambda { |arg| %w{a b}.map{|x| puts x } }
26
+ SerializableProc.new(&p2)
27
+ }.should.be raising_cannot_analyse_error("'lambda'/'proc'/'Proc.new'")
28
+ end
29
+
30
+ should "raise if line has more than 1 'proc'" do
31
+ lambda {
32
+ p1 = proc {} ; p2 = proc { |arg| %w{a b}.map{|x| puts x } }
33
+ SerializableProc.new(&p2)
34
+ }.should.be raising_cannot_analyse_error("'lambda'/'proc'/'Proc.new'")
35
+ end
36
+
37
+ should "raise if line mixes 'lambda' & 'proc'" do
38
+ lambda {
39
+ p1 = lambda {} ; p2 = proc { |arg| %w{a b}.map{|x| puts x } }
40
+ SerializableProc.new(&p2)
41
+ }.should.be raising_cannot_analyse_error("'lambda'/'proc'/'Proc.new'")
42
+ end
43
+
44
+ should "raise if line mixes 'Proc.new' & 'proc'" do
45
+ lambda {
46
+ p1 = Proc.new {} ; p2 = proc { |arg| %w{a b}.map{|x| puts x } }
47
+ SerializableProc.new(&p2)
48
+ }.should.be raising_cannot_analyse_error("'lambda'/'proc'/'Proc.new'")
49
+ end
50
+
51
+ should "raise if line mixes 'Proc.new' & 'lambda'" do
52
+ lambda {
53
+ p1 = Proc.new {} ; p2 = lambda { |arg| %w{a b}.map{|x| puts x } }
54
+ SerializableProc.new(&p2)
55
+ }.should.be raising_cannot_analyse_error("'lambda'/'proc'/'Proc.new'")
56
+ end
57
+
58
+ should "raise if line does not have lambda, proc, Proc.new or SerializableProc.new" do
59
+ lambda {
60
+ def create_serializable_proc(&block) ; SerializableProc.new(&block) ; end
61
+ create_serializable_proc { %w{a b}.map{|x| puts x } }
62
+ }.should.raise(SerializableProc::CannotAnalyseCodeError).
63
+ message.should.equal('Cannot find specified initializer !!')
64
+ end
65
+
66
+ end
67
+ end
68
+
69
+ describe '>> SerializableProc::CannotSerializeVariableError' do
70
+
71
+ extend SerializableProc::Spec::Helpers
72
+
73
+ should "raise if local variable cannot be marshalled" do
74
+ f = Tempfile.new('fake')
75
+ lambda { SerializableProc.new{ f } }.should.be raising_cannot_serialize_variable_error('f')
76
+ end
77
+
78
+ should "raise if class variable cannot be marshalled" do
79
+ @@f = Tempfile.new('fake')
80
+ lambda { SerializableProc.new{ @@f } }.should.be raising_cannot_serialize_variable_error('@@f')
81
+ end
82
+
83
+ should "raise if instance variable cannot be marshalled" do
84
+ @f = Tempfile.new('fake')
85
+ lambda { SerializableProc.new{ @f } }.should.be raising_cannot_serialize_variable_error('@f')
86
+ end
87
+
88
+ should "raise if global variable cannot be marshalled" do
89
+ $f = Tempfile.new('fake')
90
+ lambda { SerializableProc.new{ $f } }.should.be raising_cannot_serialize_variable_error('$f')
91
+ end
92
+
93
+ end
94
+
95
+ end
@@ -0,0 +1,51 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe 'Marshalling' do
4
+
5
+ describe '>> basic (wo contextual references)' do
6
+
7
+ before do
8
+ @proc = SerializableProc.new{ %w{a b}.map{|x| x } }
9
+ end
10
+
11
+ should 'be able to marshal' do
12
+ Marshal.load(Marshal.dump(@proc))
13
+ true.should.be.true # the above should execute wo error
14
+ end
15
+
16
+ should 'be able to resume proc behaviours' do
17
+ Marshal.load(Marshal.dump(@proc)).call.should.equal(%w{a b})
18
+ end
19
+
20
+ end
21
+
22
+ describe '>> with contextual references' do
23
+
24
+ should 'handle local variables' do
25
+ x, y, expected = 'awe', 'some', 'awesome'
26
+ s_proc = SerializableProc.new{ x + y }
27
+ Marshal.load(Marshal.dump(s_proc)).call.should.equal(expected)
28
+ end
29
+
30
+ should 'handle instance variables' do
31
+ @x, @y, expected = 'awe', 'some', 'awesome'
32
+ s_proc = SerializableProc.new{ @x + @y }
33
+ Marshal.load(Marshal.dump(s_proc)).call.should.equal(expected)
34
+ end
35
+
36
+ should 'handle class variables' do
37
+ @@x, @@y, expected = 'awe', 'some', 'awesome'
38
+ s_proc = SerializableProc.new{ @@x + @@y }
39
+ Marshal.load(Marshal.dump(s_proc)).call.should.equal(expected)
40
+ end
41
+
42
+ should 'handle global variables' do
43
+ $x, $y, expected = 'awe', 'some', 'awesome'
44
+ s_proc = SerializableProc.new{ $x + $y }
45
+ Marshal.load(Marshal.dump(s_proc)).call.should.equal(expected)
46
+ end
47
+
48
+ end
49
+
50
+ end
51
+
@@ -0,0 +1,159 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe 'Multiple arities serializable proc' do
4
+
5
+ extend SerializableProc::Spec::Helpers
6
+
7
+ expected_file = File.expand_path(__FILE__)
8
+ expected_code = "lambda { |lvar_arg1, lvar_arg2| [\"a\", \"b\"].map { |lvar_x| puts(lvar_x) } }"
9
+
10
+ should_handle_proc_variable expected_file, expected_code, {
11
+ # ////////////////////////////////////////////////////////////////////////
12
+ # >> Always newlinling
13
+ # ////////////////////////////////////////////////////////////////////////
14
+ __LINE__ =>
15
+ lambda do |arg1, arg2|
16
+ %w{a b}.map do |x|
17
+ puts x
18
+ end
19
+ end,
20
+ __LINE__ =>
21
+ lambda { |arg1, arg2|
22
+ %w{a b}.map{|x|
23
+ puts x
24
+ }
25
+ },
26
+ __LINE__ =>
27
+ proc do |arg1, arg2|
28
+ %w{a b}.map do |x|
29
+ puts x
30
+ end
31
+ end,
32
+ __LINE__ =>
33
+ lambda { |arg1, arg2|
34
+ %w{a b}.map{|x|
35
+ puts x
36
+ }
37
+ },
38
+ __LINE__ =>
39
+ Proc.new do |arg1, arg2|
40
+ %w{a b}.map do |x|
41
+ puts x
42
+ end
43
+ end,
44
+ __LINE__ =>
45
+ Proc.new { |arg1, arg2|
46
+ %w{a b}.map{|x|
47
+ puts x
48
+ }
49
+ },
50
+ # ////////////////////////////////////////////////////////////////////////
51
+ # >> Partial newlining
52
+ # ////////////////////////////////////////////////////////////////////////
53
+ __LINE__ =>
54
+ lambda do |arg1, arg2|
55
+ %w{a b}.map do |x| puts x end
56
+ end,
57
+ __LINE__ =>
58
+ lambda { |arg1, arg2|
59
+ %w{a b}.map{|x| puts x }
60
+ },
61
+ __LINE__ =>
62
+ proc do |arg1, arg2|
63
+ %w{a b}.map do |x| puts x end
64
+ end,
65
+ __LINE__ =>
66
+ lambda { |arg1, arg2|
67
+ %w{a b}.map{|x| puts x }
68
+ },
69
+ __LINE__ =>
70
+ Proc.new do |arg1, arg2|
71
+ %w{a b}.map do |x| puts x end
72
+ end,
73
+ __LINE__ =>
74
+ Proc.new { |arg1, arg2|
75
+ %w{a b}.map{|x| puts x }
76
+ },
77
+ # ////////////////////////////////////////////////////////////////////////
78
+ # >> No newlining
79
+ # ////////////////////////////////////////////////////////////////////////
80
+ __LINE__ =>
81
+ lambda do |arg1, arg2| %w{a b}.map do |x| puts x end end,
82
+ __LINE__ =>
83
+ lambda { |arg1, arg2| %w{a b}.map{|x| puts x } },
84
+ __LINE__ =>
85
+ proc do |arg1, arg2| %w{a b}.map do |x| puts x end end,
86
+ __LINE__ =>
87
+ lambda { |arg1, arg2| %w{a b}.map{|x| puts x } },
88
+ __LINE__ =>
89
+ Proc.new do |arg1, arg2| %w{a b}.map do |x| puts x end end,
90
+ __LINE__ =>
91
+ Proc.new { |arg1, arg2| %w{a b}.map{|x| puts x } },
92
+ }
93
+
94
+ should "handle block using do ... end [##{__LINE__}]" do
95
+ (
96
+ SerializableProc.new do |arg1, arg2|
97
+ %w{a b}.map{|x| puts x }
98
+ end
99
+ ).should.be having_expected_proc_attrs(expected_file, __LINE__ - 3, expected_code)
100
+ end
101
+
102
+ should "handle block using do ... end [##{__LINE__}]" do
103
+ (SerializableProc.new do |arg1, arg2| %w{a b}.map{|x| puts x } end).
104
+ should.be having_expected_proc_attrs(expected_file, __LINE__.pred, expected_code)
105
+ end
106
+
107
+ should "handle block using { ... } [##{__LINE__}]" do
108
+ (
109
+ SerializableProc.new { |arg1, arg2|
110
+ %w{a b}.map{|x| puts x }
111
+ }
112
+ ).should.be having_expected_proc_attrs(expected_file, __LINE__ - 3, expected_code)
113
+ end
114
+
115
+ should "handle block using { ... } [##{__LINE__}]" do
116
+ (SerializableProc.new { |arg1, arg2| %w{a b}.map{|x| puts x } }).
117
+ should.be having_expected_proc_attrs(expected_file, __LINE__.pred, expected_code)
118
+ end
119
+
120
+ should "handle fanciful initializing with lambda { ... } [##{__LINE__}]" do
121
+ (SerializableProc.new(&(lambda { |arg1, arg2| %w{a b}.map{|x| puts x } }))).
122
+ should.be having_expected_proc_attrs(expected_file, __LINE__.pred, expected_code)
123
+ end
124
+
125
+ should "handle fanciful initializing with lambda do ... end [##{__LINE__}]" do
126
+ (
127
+ SerializableProc.new(&(lambda do |arg1, arg2|
128
+ %w{a b}.map{|x| puts x }
129
+ end))
130
+ ).should.be having_expected_proc_attrs(expected_file, __LINE__ - 3, expected_code)
131
+ end
132
+
133
+ should "handle fanciful initializing with proc { ... } [##{__LINE__}]" do
134
+ (SerializableProc.new(&(proc { |arg1, arg2| %w{a b}.map{|x| puts x } }))).
135
+ should.be having_expected_proc_attrs(expected_file, __LINE__.pred, expected_code)
136
+ end
137
+
138
+ should "handle fanciful initializing with proc do ... end [##{__LINE__}]" do
139
+ (
140
+ SerializableProc.new(&(proc do |arg1, arg2|
141
+ %w{a b}.map{|x| puts x }
142
+ end))
143
+ ).should.be having_expected_proc_attrs(expected_file, __LINE__ - 3, expected_code)
144
+ end
145
+
146
+ should "handle fanciful initializing with Proc.new { ... } [##{__LINE__}]" do
147
+ (SerializableProc.new(&(Proc.new { |arg1, arg2| %w{a b}.map{|x| puts x } }))).
148
+ should.be having_expected_proc_attrs(expected_file, __LINE__.pred, expected_code)
149
+ end
150
+
151
+ should "handle fanciful initializing with Proc.new do ... end [##{__LINE__}]" do
152
+ (
153
+ SerializableProc.new(&(Proc.new do |arg1, arg2|
154
+ %w{a b}.map{|x| puts x }
155
+ end))
156
+ ).should.be having_expected_proc_attrs(expected_file, __LINE__ - 3, expected_code)
157
+ end
158
+
159
+ end