hijacker 0.1.0 → 0.2.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.
data/Changelog ADDED
@@ -0,0 +1,11 @@
1
+ === Version 0.2.0 / 2010-12-05
2
+
3
+ * enhancements
4
+ * report blocks passed to hijacked methods as regular arguments
5
+ * report raised exceptions in hijacked methods
6
+ * automatically load custom handler classes in these paths:
7
+ * ./.hijacker/**/*.rb
8
+ * ~/.hijacker/**/*.rb
9
+
10
+ * bug fixes
11
+ * fixed many issues with 1.8.7
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- hijacker (0.1.0)
4
+ hijacker (0.2.0)
5
5
  trollop
6
6
 
7
7
  GEM
data/Readme.md CHANGED
@@ -16,7 +16,9 @@ creative! :)
16
16
  (See the "Extending hijacker blabla" part below to know how to write your own
17
17
  handlers)
18
18
 
19
- Hijacker is tested with Ruby 1.8.7, 1.9.2, JRuby 1.5.3 and Rubinius 1.1.
19
+ Hijacker is tested with Ruby 1.8.7, 1.9.2, and JRuby 1.5.3. Unfortunately there
20
+ are some issues with Rubinius, mostly due to the metaprogramming stuff, which I
21
+ will definitely look into.
20
22
 
21
23
  ##Install and configure
22
24
 
@@ -36,7 +38,7 @@ And that's it! Oooor not. You have to spawn your server. In the command line:
36
38
 
37
39
  hijacker <handler>
38
40
 
39
- Where <handler> must be a registered handler (for now there is only 'logger').
41
+ Where \<handler\> must be a registered handler (for now there is only 'logger').
40
42
  So you type:
41
43
 
42
44
  hijacker logger
@@ -44,6 +46,15 @@ So you type:
44
46
  And it will output the URI for this super fancy hijacker logging server.
45
47
  *Remember this URI* and pass it to your configuration block!
46
48
 
49
+ If you have some custom handler, you should send me a pull request! In case you
50
+ don't want to, Hijacker automatically requires all ruby files inside these
51
+ paths:
52
+
53
+ ./.hijacker/**/**.rb
54
+ ~/.hijacker/**/**.rb
55
+
56
+ So you put your handlers in there and have fun! :)
57
+
47
58
  Some options you can pass to the server command:
48
59
 
49
60
  hijacker <handler> --port 1234 (spawn the server in port 1234 rather than 8787)
@@ -122,13 +133,13 @@ Of course, you can specify a particular server uri for a block, with #spying:
122
133
  It is really easy to write your own handlers. Why don't you write one and send
123
134
  me a pull request? I mean now. What are you waiting for, why are you still reading?
124
135
 
125
- Ok, maybe a bit of explanation on that. Handlers live here:
126
-
127
- lib/hijacker/handlers/your_handler.rb
136
+ Ok, maybe a bit of explanation on that. Handlers are automatically loaded from
137
+ here:
128
138
 
129
- They are autoloaded and automatically registered, so all you have to do is
130
- write them like this:
139
+ ./.hijacker/**/*.rb
140
+ ~/.hijacker/**/*.rb
131
141
 
142
+ They are automatically registered, so all you have to do is write them like this:
132
143
 
133
144
  module Hijacker
134
145
  class MyHandler < Handler # Yes, you have to subclass Hijacker::Handler!
@@ -154,10 +165,14 @@ write them like this:
154
165
  #
155
166
  # args [{:inspect => '3', :class => 'Fixnum'},
156
167
  # {:inspect => '"string"', :class => 'String'}]
168
+ #
169
+ # retval {:inspect => ':bar', :class => 'Symbol'}
170
+ # (note: retval will be nil if the method raised)
157
171
  #
158
- # retval [{:inspect => ':bar', :class => 'Symbol'}]
172
+ # raised {:inspect => 'oops', :class => 'StandardError'}
173
+ # (note: raised will be nil unless the method raised, obviously)
159
174
  #
160
- # object [{:inspect => '#<MyClass:0x003457>', :class => 'MyClass'}]
175
+ # object {:inspect => '#<MyClass:0x003457>', :class => 'MyClass'}
161
176
  #
162
177
  def handle(method, args, retval, object)
163
178
  # Do what you want with these!
data/bin/hijacker CHANGED
@@ -58,7 +58,7 @@ puts welcome.join("#{ANSI[:RESET]} ") + "\n"
58
58
 
59
59
  # We need the uri of the service to connect a client
60
60
  instructions = "Put this code in the configuration of your ruby program #{ANSI[:BOLD]}before any call to Hijacker#{ANSI[:RESET]}:\n\n"
61
- instructions += "\t" + "Hijacker.config do\n"
61
+ instructions += "\t" + "Hijacker.configure do\n"
62
62
  instructions += "\t" + " uri '#{DRb.uri}'\n"
63
63
  instructions += "\t" + "end\n\n"
64
64
  puts instructions
data/lib/hijacker.rb CHANGED
@@ -1,113 +1,34 @@
1
1
  require 'drb'
2
2
  require 'trollop'
3
+ require 'hijacker/exceptions'
4
+ require 'hijacker/method_definer'
5
+ require 'hijacker/spy'
3
6
  require 'hijacker/config'
4
7
  require 'hijacker/handler'
5
8
 
6
9
  module Hijacker
7
10
 
8
- # Methods that won't be hijacked in any case
9
- REJECTED_METHODS = (Object.instance_methods | Module.methods | %w{< <= > >= __original_[\w\d]+ [^\w\d]+})
10
- FORBIDDEN_CLASSES = [Array, Hash, String, Fixnum, Float, Numeric, Symbol, Proc, Class, Object, BasicObject, Module]
11
-
12
11
  class << self
13
12
 
14
- def spying(*args, &block)
15
- raise "No block given" unless block
16
- Hijacker.spy(*args)
17
- block.call
18
- Hijacker.restore(args.first)
19
- end
20
-
21
- def spy(object, options = {})
22
- raise "Cannot spy on the following forbidden classes: #{FORBIDDEN_CLASSES.map(&:to_s).join(', ')}" if FORBIDDEN_CLASSES.include?(object)
23
- rejection = /^(#{REJECTED_METHODS.join('|')})/
24
- only = options[:only]
25
- uri = options[:uri]
26
- custom_rejection = options[:reject] if options[:reject].is_a?(Regexp)
27
-
28
- inst_methods = guess_instance_methods_from(object).reject{|m| (m =~ rejection)}.reject{|m| m =~ custom_rejection}
29
- sing_methods = guess_class_methods_from(object).reject{|m| m =~ rejection}.reject{|m| m =~ custom_rejection}
30
-
31
- receiver = if object.is_a?(Class)
32
- object
33
- else
34
- (class << object; self; end)
35
- end
36
-
37
- inst_methods.each do |met|
38
- receiver.send(:alias_method, :"__original_#{met}", :"#{met}")
39
- receiver.send(:undef_method, :"#{met}")
40
- receiver.class_eval <<EOS
41
- def #{met}(*args, &blk)
42
- __original_#{met}(*args,&blk).tap do |retval|
43
- Hijacker.register :#{met}, args, retval, self, #{uri.inspect}
44
- end
45
- end
46
- EOS
47
- end unless options[:only] == :singleton_methods
13
+ include MethodDefiner
14
+ private :define_hijacked
48
15
 
49
- receiver = (class << object; self; end)
50
- sing_methods.each do |met|
51
- receiver.send(:alias_method, :"__original_#{met}", :"#{met}")
52
- receiver.send(:undef_method, :"#{met}")
53
- receiver.class_eval <<EOS
54
- def #{met}(*args, &blk)
55
- __original_#{met}(*args,&blk).tap do |retval|
56
- Hijacker.register :#{met}, args, retval, self, #{uri.inspect}
57
- end
58
- end
59
- EOS
60
- end unless options[:only] == :instance_methods
16
+ include Spy
17
+ public :spy, :spying, :restore
61
18
 
62
- end
63
-
64
- def restore(object)
65
- receiver = if object.is_a?(Class)
66
- object
67
- else
68
- (class << object; self; end)
69
- end
70
- guess_instance_methods_from(object).select{|m| m =~ /__original_/}.each do |met|
71
- met = met.to_s.gsub!("__original_", "")
72
- receiver.send(:undef_method, :"#{met}")
73
- receiver.send(:alias_method, :"#{met}", :"__original_#{met}")
74
- end
75
-
76
- receiver = (class << object; self; end)
77
- guess_class_methods_from(object).select{|m| m =~ /__original_/}.each do |met|
78
- met = met.to_s.gsub!("__original_", "")
79
- receiver.send(:undef_method, :"#{met}")
80
- receiver.send(:alias_method, :"#{met}", :"__original_#{met}")
81
- end
82
- end
83
-
84
- def register(method, args, retval, object, uri = nil)
19
+ def register(method, args, retval, raised, object, uri = nil)
85
20
  args.map! do |arg|
86
21
  {:inspect => arg.inspect, :class => arg.class.name}
87
22
  end
88
- retval = {:inspect => retval.inspect, :class => retval.class.name}
89
- object = {:inspect => object.inspect, :class => object.class.name}
90
-
91
- server = DRbObject.new nil, (uri || self.drb_uri)
92
- server.handle method, args, retval, object
93
- end
94
-
95
- private
96
-
97
- def guess_instance_methods_from(object)
98
- if object.is_a?(Class)
99
- object.instance_methods
23
+ if raised
24
+ raised = {:inspect => raised.message, :class => raised.class.name}
100
25
  else
101
- object.methods
26
+ retval = {:inspect => retval.inspect, :class => retval.class.name}
102
27
  end
103
- end
28
+ object = {:inspect => object.inspect, :class => object.class.name}
104
29
 
105
- def guess_class_methods_from(object)
106
- if object.is_a?(Class)
107
- object.methods
108
- else
109
- []
110
- end
30
+ server = DRbObject.new nil, (uri || self.drb_uri)
31
+ server.handle method, args, retval, raised, object
111
32
  end
112
33
 
113
34
  end
@@ -11,8 +11,8 @@ module Hijacker
11
11
  def drb_uri
12
12
  begin
13
13
  @@drb_uri
14
- rescue
15
- raise "Neither a global nor a local Hijacker server URI is configured. Please refer to the README to find out how to do this."
14
+ rescue NameError
15
+ raise UndefinedUriError, "Neither a global nor a local Hijacker server URI is configured. Please refer to the README to find out how to do this."
16
16
  end
17
17
  end
18
18
  end
@@ -0,0 +1,3 @@
1
+ module Hijacker
2
+ class UndefinedUriError < StandardError; end;
3
+ end
@@ -21,7 +21,7 @@ module Hijacker
21
21
  @opts = opts
22
22
  end
23
23
 
24
- def handle(method, args, retval, object)
24
+ def handle(method, args, retval, raised, object)
25
25
  # Parameters received
26
26
  #
27
27
  # method :foo
@@ -29,9 +29,14 @@ module Hijacker
29
29
  # args [{:inspect => '3', :class => 'Fixnum'},
30
30
  # {:inspect => '"string"', :class => 'String'}]
31
31
  #
32
- # retval [{:inspect => ':bar', :class => 'Symbol'}]
32
+ # retval {:inspect => ':bar', :class => 'Symbol'}
33
+ #
34
+ # - In case the method raised something, retval will be nil,
35
+ # and the exception info will be available in raised:
33
36
  #
34
- # object [{:inspect => '#<MyClass:0x003457>', :class => 'MyClass'}]
37
+ # raised {:inspect => 'wrong number of arguments (0 for 2)', :class => 'ArgumentError'}
38
+ #
39
+ # object {:inspect => '#<MyClass:0x003457>', :class => 'MyClass'}
35
40
  #
36
41
  raise NotImplementedError.new("You are supposed to subclass Handler")
37
42
  end
@@ -52,7 +57,14 @@ module Hijacker
52
57
  end
53
58
  end
54
59
 
55
- # Automatically load all handlers
56
- Dir[File.dirname(File.join(File.dirname(__FILE__), 'handlers', '**', '*.rb'))].entries.each do |handler|
60
+ # Automatically load all handlers in the following paths:
61
+ #
62
+ # ./.hijacker/**/*.rb
63
+ # ~/.hijacker/**/*.rb
64
+ # lib/handlers/**/*.rb
65
+ #
66
+ (Dir[File.dirname(File.join(Dir.pwd, '.hijacker', '**', '*.rb'))] + \
67
+ Dir[File.dirname(File.expand_path(File.join('~', '.hijacker', '**', '*.rb')))] + \
68
+ Dir[File.dirname(File.join(File.dirname(__FILE__), 'handlers', '**', '*.rb'))]).entries.each do |handler|
57
69
  require(handler) && Hijacker::Handler.register_handler(handler)
58
70
  end
@@ -18,7 +18,7 @@ module Hijacker
18
18
  :CYAN=>"\e[36m", :LCYAN=>"\e[1;36m",
19
19
  :WHITE=>"\e[37m"}
20
20
 
21
- def handle(method, args, retval, object)
21
+ def handle(method, args, retval, raised, object)
22
22
  out = []
23
23
  out << ANSI[:BOLD] + ANSI[:UNDERLINE] + "#{Time.now}" unless opts[:without_timestamps]
24
24
  out << ANSI[:CYAN] + object[:inspect]
@@ -32,10 +32,17 @@ module Hijacker
32
32
  ANSI[:RESET]
33
33
  end.join(', ')
34
34
  end
35
- out << "and returned"
36
- out << ANSI[:BLUE] + retval[:inspect]
37
- out << ANSI[:LBLUE] + "(#{retval[:class]})" unless opts[:without_classes]
38
- out << ANSI[:RESET] + "\n"
35
+ if raised
36
+ out << "and raised"
37
+ out << ANSI[:BLUE] + raised[:inspect]
38
+ out << ANSI[:LBLUE] + "(#{raised[:class]})" unless opts[:without_classes]
39
+ out << ANSI[:RESET] + "\n"
40
+ else
41
+ out << "and returned"
42
+ out << ANSI[:BLUE] + retval[:inspect]
43
+ out << ANSI[:LBLUE] + "(#{retval[:class]})" unless opts[:without_classes]
44
+ out << ANSI[:RESET] + "\n"
45
+ end
39
46
  stdout.print out.join("#{ANSI[:RESET]} ")
40
47
  end
41
48
 
@@ -0,0 +1,27 @@
1
+ module Hijacker
2
+ module MethodDefiner
3
+
4
+ def define_hijacked(methods, receiver, uri)
5
+ methods.each do |met|
6
+ receiver.send(:alias_method, :"__original_#{met}", :"#{met}")
7
+ receiver.send(:undef_method, :"#{met}")
8
+ writer = (met =~ /=$/)
9
+ receiver.class_eval <<EOS
10
+ def #{met}(#{writer ? 'arg' : '*args, &blk'})
11
+ _args = #{writer ? '[arg]' : 'args'}
12
+ _args += [blk] if block_given?
13
+ begin
14
+ __original_#{met}(#{writer ? 'arg' : '*args, &blk'}).tap do |retval|
15
+ Hijacker.register :#{met}, _args, retval, nil, self, #{uri.inspect}
16
+ end
17
+ rescue=>error
18
+ Hijacker.register :#{met}, _args, nil, error, self, #{uri.inspect}
19
+ raise error
20
+ end
21
+ end
22
+ EOS
23
+ end
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,75 @@
1
+ module Hijacker
2
+ module Spy
3
+
4
+ REJECTED_METHODS = (Object.instance_methods | Module.methods | %w{__original_[\w\d]+})
5
+ FORBIDDEN_CLASSES = [Array, Hash, String, Fixnum, Float, Numeric, Symbol, Proc, Class, Object, Module]
6
+
7
+ def spying(*args, &block)
8
+ raise "No block given" unless block
9
+ Hijacker.spy(*args)
10
+ block.call
11
+ Hijacker.restore(args.first)
12
+ end
13
+
14
+ def spy(object, options = {})
15
+ raise "Cannot spy on the following forbidden classes: #{FORBIDDEN_CLASSES.map(&:to_s).join(', ')}" if FORBIDDEN_CLASSES.include?(object)
16
+ rejection = /^(#{REJECTED_METHODS.join('|')})/
17
+ only = options[:only]
18
+ uri = options[:uri]
19
+ custom_rejection = options[:reject] if options[:reject].is_a?(Regexp)
20
+
21
+ inst_methods = guess_instance_methods_from(object).reject{|m| m.to_s =~ rejection}.reject{|m| m.to_s =~ custom_rejection}
22
+ sing_methods = guess_singleton_methods_from(object).reject{|m| m.to_s =~ rejection}.reject{|m| m.to_s =~ custom_rejection}
23
+
24
+ receiver = if object.is_a?(Class)
25
+ object
26
+ else
27
+ (class << object; self; end)
28
+ end
29
+
30
+ define_hijacked(inst_methods, receiver, uri) unless options[:only] == :singleton_methods
31
+ receiver = (class << object; self; end)
32
+ define_hijacked(sing_methods, receiver, uri) unless options[:only] == :instance_methods
33
+
34
+ end
35
+
36
+ def restore(object)
37
+ receiver = if object.is_a?(Class)
38
+ object
39
+ else
40
+ (class << object; self; end)
41
+ end
42
+ guess_instance_methods_from(object).select{|m| m.to_s =~ /__original_/}.each do |met|
43
+ met = met.to_s.gsub!("__original_", "")
44
+ receiver.send(:undef_method, :"#{met}")
45
+ receiver.send(:alias_method, :"#{met}", :"__original_#{met}")
46
+ end
47
+
48
+ receiver = (class << object; self; end)
49
+ guess_singleton_methods_from(object).select{|m| m.to_s =~ /__original_/}.each do |met|
50
+ met = met.to_s.gsub!("__original_", "")
51
+ receiver.send(:undef_method, :"#{met}")
52
+ receiver.send(:alias_method, :"#{met}", :"__original_#{met}")
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def guess_instance_methods_from(object)
59
+ if object.is_a?(Class)
60
+ object.instance_methods
61
+ else
62
+ object.methods
63
+ end
64
+ end
65
+
66
+ def guess_singleton_methods_from(object)
67
+ if object.is_a?(Class)
68
+ object.methods
69
+ else
70
+ []
71
+ end
72
+ end
73
+
74
+ end
75
+ end
@@ -1,3 +1,3 @@
1
1
  module Hijacker
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -0,0 +1,98 @@
1
+ require 'spec_helper'
2
+
3
+ # TODO: Turn into green cukes!
4
+ describe Hijacker, "acceptance specs" do
5
+ describe "#spy - #restore" do
6
+
7
+ describe "hijacking a Class" do
8
+ describe "instance methods" do
9
+ before(:each) do
10
+ Hijacker.spy(MyClass, :only => :instance_methods)
11
+ end
12
+ it "registers method calls without arguments" do
13
+ Hijacker.should_receive(:register).with(:foo, [], 7, nil, kind_of(MyClass), nil).ordered
14
+ MyClass.new.foo.should == 7
15
+ end
16
+ it "registers method calls with arguments" do
17
+ Hijacker.should_receive(:register).with(:bar, [2, "string"], "string", nil, kind_of(MyClass), nil).ordered
18
+ MyClass.new.bar(2, "string").should == "string"
19
+ end
20
+ after(:each) do
21
+ Hijacker.restore(MyClass)
22
+ end
23
+ end
24
+ describe "class methods" do
25
+ before(:each) do
26
+ Hijacker.spy(MyClass)
27
+ end
28
+ it "registers method calls without arguments" do
29
+ Hijacker.should_receive(:register).with(:foo, [], 7, nil, kind_of(Class), nil).ordered
30
+ MyClass.foo.should == 7
31
+ end
32
+ it "registers method calls with arguments" do
33
+ Hijacker.should_receive(:register).with(:bar, [2, "string"], "string", nil, kind_of(Class), nil).ordered
34
+ MyClass.bar(2, "string").should == "string"
35
+ end
36
+ after(:each) do
37
+ Hijacker.restore(MyClass)
38
+ end
39
+ end
40
+ describe "forbidden classes (are not hijacked)" do
41
+ [Array, Hash, String, Fixnum, Float, Numeric, Symbol].each do |forbidden|
42
+ it "protects #{forbidden}" do
43
+ expect {
44
+ Hijacker.spy(forbidden)
45
+ }.to raise_error
46
+ end
47
+ end
48
+ end
49
+ end
50
+ describe "hijacking an object" do
51
+ describe "instance methods" do
52
+ let(:object) { MyClass.new }
53
+
54
+ before(:each) do
55
+ def object.my_method
56
+ 8
57
+ end
58
+ def object.my_method_with_args(a,b)
59
+ b
60
+ end
61
+ Hijacker.spy(object)
62
+ end
63
+ it "registers method calls without arguments" do
64
+ Hijacker.should_receive(:register).with(:foo, [], 7, nil, kind_of(MyClass), nil).ordered
65
+ Hijacker.should_receive(:register).with(:my_method, [], 8, nil, kind_of(MyClass), nil).ordered
66
+
67
+ object.foo.should == 7
68
+ object.my_method.should == 8
69
+ end
70
+ it "registers method calls with arguments" do
71
+ Hijacker.should_receive(:register).with(:bar, [2, "string"], "string", nil, kind_of(MyClass), nil).ordered
72
+ Hijacker.should_receive(:register).with(:my_method_with_args, [2, "string"], "string", nil, kind_of(MyClass), nil).ordered
73
+
74
+ object.bar(2, "string").should == "string"
75
+ object.my_method_with_args(2, "string").should == "string"
76
+ end
77
+ it "works well with writers" do
78
+ Hijacker.should_receive(:register).with(:baz=, [2], 2, nil, kind_of(MyClass), nil).ordered
79
+ object.baz = 2
80
+ end
81
+ it "records exceptions" do
82
+ Hijacker.should_receive(:register).with(:nasty_method, [], nil, kind_of(StandardError), kind_of(MyClass), nil).ordered
83
+ expect {
84
+ object.nasty_method
85
+ }.to raise_error(StandardError)
86
+ end
87
+ it "does not affect other instances of the object's class" do
88
+ Hijacker.should_not_receive(:register)
89
+ MyClass.new.foo.should == 7
90
+ end
91
+ after(:each) do
92
+ Hijacker.restore(object)
93
+ end
94
+ end
95
+ end
96
+
97
+ end
98
+ end
@@ -3,11 +3,34 @@ require 'spec_helper'
3
3
  describe Hijacker, "configuration" do
4
4
 
5
5
  describe "#configure" do
6
- it 'accepts a block with the \'uri\' configuration option' do
7
- Hijacker.configure do
8
- uri 'druby://localhost:8787'
6
+ it 'evaluates the passed block' do
7
+ block = Proc.new {}
8
+ Hijacker.should_receive(:instance_eval).with(&block).once
9
+ Hijacker.configure(&block)
10
+ end
11
+ end
12
+
13
+ describe "#uri" do
14
+ it 'assigns the DRb uri as a class variable' do
15
+ Hijacker.uri 'druby://localhost:8787'
16
+ Hijacker.send(:class_variable_get, :@@drb_uri).should == 'druby://localhost:8787'
17
+ end
18
+ end
19
+
20
+ describe "#drb_uri" do
21
+ context "when the class variable is set" do
22
+ it 'is an accessor to it' do
23
+ Hijacker.send(:class_variable_set, :@@drb_uri, 'druby://localhost:8787')
24
+ Hijacker.drb_uri.should == 'druby://localhost:8787'
25
+ end
26
+ end
27
+ context "otherwise" do
28
+ it 'raises an error' do
29
+ Hijacker.send(:remove_class_variable, :@@drb_uri)
30
+ expect {
31
+ Hijacker.drb_uri
32
+ }.to raise_error(Hijacker::UndefinedUriError)
9
33
  end
10
- Hijacker.drb_uri.should == 'druby://localhost:8787'
11
34
  end
12
35
  end
13
36
 
@@ -9,8 +9,13 @@ module Hijacker
9
9
  subject.opts.should == {:my => :option}
10
10
  end
11
11
 
12
- describe "#handle" do
12
+ it { should respond_to(:opts)}
13
+
14
+ it "includes DRb::DRbUndumpled" do
15
+ Handler.included_modules.should include(DRb::DRbUndumped)
16
+ end
13
17
 
18
+ describe "#handle" do
14
19
  let(:args) do
15
20
  [:bar,
16
21
  [
@@ -18,6 +23,7 @@ module Hijacker
18
23
  {:inspect => "\"string\"", :class => "String"},
19
24
  ],
20
25
  {:inspect => "\"retval\"", :class => "String"},
26
+ nil,
21
27
  {:inspect => "MyClass", :class => "Class"}]
22
28
  end
23
29
 
@@ -31,12 +37,29 @@ module Hijacker
31
37
  describe "class methods" do
32
38
 
33
39
  describe "#register_handler" do
34
- it 'registers a loaded handler' do
35
- Hijacker::Handler.register_handler "/path/to/my/handlers/benchmark.rb"
36
- Hijacker::Handler.handlers.should include('benchmark')
40
+ context "when given a relative path" do
41
+ it 'registers that file as a handler' do
42
+ Hijacker::Handler.register_handler "/path/to/my/handlers/benchmark.rb"
43
+ Hijacker::Handler.handlers.should include('benchmark')
44
+ end
45
+ end
46
+ context "when given an absolute path" do
47
+ it 'registers that file as well' do
48
+ Hijacker::Handler.register_handler "~/.handlers/custom_handler.rb"
49
+ Hijacker::Handler.handlers.should include('custom_handler')
50
+ end
37
51
  end
38
52
  end
39
-
53
+
54
+ describe "#handlers" do
55
+ it 'is an accessor to the class variable' do
56
+ handlers = [double('handler'),
57
+ double('another handler')]
58
+ Handler.send(:class_variable_set, :@@handlers, handlers)
59
+ Handler.handlers.should == handlers
60
+ end
61
+ end
62
+
40
63
  end
41
64
 
42
65
  end
@@ -27,6 +27,7 @@ module Hijacker
27
27
  {:inspect => "\"string\"", :class => "String"},
28
28
  ],
29
29
  {:inspect => "\"retval\"", :class => "String"},
30
+ nil,
30
31
  {:inspect => "MyClass", :class => "Class"}]
31
32
  end
32
33
 
@@ -84,6 +85,46 @@ module Hijacker
84
85
  end
85
86
  end
86
87
  end
88
+ context "when given a raised exception" do
89
+
90
+ let(:args) do
91
+ [:bar,
92
+ [
93
+ {:inspect => "2", :class => "Fixnum"},
94
+ {:inspect => "\"string\"", :class => "String"},
95
+ ],
96
+ nil,
97
+ {:inspect => "wrong number of arguments (0 for 2)", :class => "ArgumentError"},
98
+ {:inspect => "MyClass", :class => "Class"}]
99
+ end
100
+
101
+ it "prints it instead of the return value" do
102
+ out = StringIO.new
103
+ subject.stub(:stdout).and_return out
104
+
105
+ Time.stub(:now).and_return Time.parse('2010-11-20')
106
+
107
+ subject.handle(*args)
108
+
109
+ ["00:00:00 +0100",
110
+ "MyClass",
111
+ "(Class)",
112
+ "received",
113
+ ":bar",
114
+ "with",
115
+ "2",
116
+ "(Fixnum)",
117
+ "\"string\"",
118
+ "(String)",
119
+ "and raised",
120
+ "wrong number of arguments (0 for 2)",
121
+ "(ArgumentError)"].each do |str|
122
+ out.string.should include(str)
123
+ end
124
+ end
125
+
126
+ end
127
+
87
128
  end
88
129
 
89
130
  end
@@ -0,0 +1,113 @@
1
+ require 'spec_helper'
2
+
3
+ module Hijacker
4
+ describe MethodDefiner do
5
+
6
+ describe "#define_hijacked" do
7
+ let(:receiver) do
8
+ class SomeClass
9
+ def foo
10
+ 7
11
+ end
12
+ def bar(a, b)
13
+ a + b
14
+ end
15
+ def baz=(value)
16
+ @baz = value
17
+ end
18
+ def method_with_block(argument)
19
+ yield if block_given?
20
+ end
21
+ def method_that_raises
22
+ raise StandardError, "Something went wrong"
23
+ end
24
+ end
25
+ SomeClass
26
+ end
27
+
28
+ before(:each) do
29
+ Hijacker.stub(:register)
30
+ end
31
+
32
+ after(:each) do
33
+ Hijacker.restore(receiver)
34
+ end
35
+
36
+ it 'saves original methods on the receiver' do
37
+ Hijacker.send(:define_hijacked, [:foo, :bar], receiver, nil)
38
+ instance = receiver.new
39
+ instance.should respond_to(:__original_foo, :__original_bar)
40
+ instance.should_not respond_to(:__original_baz)
41
+ end
42
+
43
+ it 'creates aliased methods' do
44
+ Hijacker.send(:define_hijacked, [:foo], receiver, nil)
45
+ instance = receiver.new
46
+
47
+ instance.should_receive(:__original_foo).once
48
+ instance.foo
49
+ instance.should_not_receive(:__original_bar)
50
+ instance.bar(9,10)
51
+ end
52
+
53
+ describe "registering method calls" do
54
+ context "with no arguments" do
55
+ it "registers the method call" do
56
+ Hijacker.send(:define_hijacked, [:foo], receiver, nil)
57
+ instance = receiver.new
58
+ Hijacker.should_receive(:register).with :foo,
59
+ [],
60
+ 7,
61
+ nil,
62
+ instance,
63
+ nil
64
+ instance.foo.should == 7
65
+ end
66
+ end
67
+ context "with arguments" do
68
+ it "registers the method call" do
69
+ Hijacker.send(:define_hijacked, [:bar], receiver, nil)
70
+ instance = receiver.new
71
+ Hijacker.should_receive(:register).with :bar,
72
+ [1,2],
73
+ 3,
74
+ nil,
75
+ instance,
76
+ nil
77
+ instance.bar(1,2).should == 3
78
+ end
79
+ end
80
+ context "with arguments and a block" do
81
+ it "registers the method call" do
82
+ Hijacker.send(:define_hijacked, [:method_with_block], receiver, nil)
83
+ instance = receiver.new
84
+ Hijacker.should_receive(:register).with :method_with_block,
85
+ [1,kind_of(Proc)],
86
+ 3,
87
+ nil,
88
+ instance,
89
+ nil
90
+ instance.method_with_block(1) do
91
+ 3
92
+ end.should == 3
93
+ end
94
+ end
95
+ context "raising an exception" do
96
+ it "registers the method call" do
97
+ Hijacker.send(:define_hijacked, [:method_that_raises], receiver, nil)
98
+ instance = receiver.new
99
+ Hijacker.should_receive(:register).with :method_that_raises,
100
+ [],
101
+ nil,
102
+ kind_of(StandardError),
103
+ instance,
104
+ nil
105
+ expect {
106
+ instance.method_that_raises
107
+ }.to raise_error(StandardError)
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,156 @@
1
+ require 'spec_helper'
2
+
3
+ class MyClass
4
+ def self.foo
5
+ 3 + 4
6
+ end
7
+ def self.bar(a,b)
8
+ b
9
+ end
10
+
11
+ def foo
12
+ 3 + 4
13
+ end
14
+ def bar(a,b)
15
+ b
16
+ end
17
+
18
+ def baz=(value)
19
+ @value = value
20
+ end
21
+
22
+ def nasty_method
23
+ raise StandardError, "Something went wrong"
24
+ end
25
+
26
+ end unless defined?(MyClass)
27
+
28
+ module Hijacker
29
+ describe Spy do
30
+ let(:object) { MyClass.new }
31
+ let(:klass) { MyClass }
32
+ let(:inst_methods) { [:foo, :bar] }
33
+ let(:sing_methods) { [:foo, :bar] }
34
+
35
+ describe "#spying" do
36
+ it 'runs a block spying on a particular object' do
37
+ blk = lambda {
38
+ MyClass.foo
39
+ }
40
+ Hijacker.should_receive(:spy).with(MyClass).once.ordered
41
+ MyClass.should_receive(:foo).once.ordered
42
+ Hijacker.should_receive(:restore).with(MyClass).once.ordered
43
+
44
+ Hijacker.spying(MyClass, &blk)
45
+ end
46
+
47
+ it 'raises if no block given' do
48
+ expect {
49
+ Hijacker.spying(MyClass)
50
+ }.to raise_error("No block given")
51
+ end
52
+ end
53
+
54
+ describe "#spy" do
55
+
56
+ context "when given a class" do
57
+ before(:each) do
58
+ Hijacker.should_receive(:guess_instance_methods_from).with(klass).and_return(inst_methods)
59
+ Hijacker.should_receive(:guess_singleton_methods_from).with(klass).and_return(sing_methods)
60
+ end
61
+
62
+ let(:metaclass) { (class << klass; self; end) }
63
+
64
+ it 'calls define_hijacked on all methods' do
65
+ Hijacker.should_receive(:define_hijacked).with(inst_methods, klass, nil).once
66
+ Hijacker.should_receive(:define_hijacked).with(sing_methods, metaclass, nil).once
67
+
68
+ Hijacker.spy(klass)
69
+ end
70
+ context "with :only => :singleton_methods" do
71
+ it 'calls define_hijacked only on singleton methods' do
72
+ Hijacker.should_not_receive(:define_hijacked).with(inst_methods, klass, nil)
73
+ Hijacker.should_receive(:define_hijacked).with(sing_methods, metaclass, nil).once
74
+
75
+ Hijacker.spy(klass, :only => :singleton_methods)
76
+ end
77
+ end
78
+ context "with :only => :instance_methods" do
79
+ it 'calls define_hijacked only on instance methods' do
80
+ Hijacker.should_receive(:define_hijacked).with(inst_methods, klass, nil)
81
+ Hijacker.should_not_receive(:define_hijacked).with(sing_methods, metaclass, nil)
82
+
83
+ Hijacker.spy(klass, :only => :instance_methods)
84
+ end
85
+ end
86
+ end
87
+ context "when given an object" do
88
+ before(:each) do
89
+ Hijacker.stub(:guess_instance_methods_from).with(object).and_return(inst_methods)
90
+ Hijacker.stub(:guess_singleton_methods_from).with(object).and_return(sing_methods)
91
+ end
92
+
93
+ let(:metaclass) { (class << object; self; end) }
94
+ it 'calls define_hijacked on all methods' do
95
+ Hijacker.should_receive(:define_hijacked).with(inst_methods, metaclass, nil).once
96
+ Hijacker.should_receive(:define_hijacked).with(sing_methods, metaclass, nil).once
97
+
98
+ Hijacker.spy(object)
99
+ end
100
+ context "with :only => :singleton_methods" do
101
+ it 'calls define_hijacked only on singleton methods' do
102
+ Hijacker.should_receive(:define_hijacked).with(sing_methods, metaclass, nil).once
103
+
104
+ Hijacker.spy(object, :only => :singleton_methods)
105
+ end
106
+ end
107
+ context "with :only => :instance_methods" do
108
+ it 'calls define_hijacked only on instance methods' do
109
+ Hijacker.should_receive(:define_hijacked).with(inst_methods, metaclass, nil).once
110
+
111
+ Hijacker.spy(object, :only => :instance_methods)
112
+ end
113
+ end
114
+ end
115
+ context "when given a forbidden class" do
116
+ it "raises" do
117
+ expect {
118
+ Hijacker.spy(Array)
119
+ }.to raise_error(StandardError)
120
+ end
121
+ end
122
+ it "rejects methods from REJECTED_METHODS constant" do
123
+ inst_methods_with_some_rejected = inst_methods | [:instance_eval, :__original_something]
124
+ sing_methods_with_some_rejected = sing_methods | [:instance_eval, :__original_something]
125
+
126
+ Hijacker.should_receive(:guess_instance_methods_from).with(object).and_return(inst_methods_with_some_rejected)
127
+ Hijacker.should_receive(:guess_singleton_methods_from).with(object).and_return(sing_methods_with_some_rejected)
128
+
129
+ Hijacker.should_receive(:define_hijacked).with(inst_methods, kind_of(Class), nil).once
130
+ Hijacker.should_receive(:define_hijacked).with(sing_methods, kind_of(Class), nil).once
131
+
132
+ Hijacker.spy(object)
133
+ end
134
+ end
135
+
136
+ describe "#restore" do
137
+ it "restores all methods from the object" do
138
+ inst_methods_with_some_hijacked = inst_methods | [:__original_foo, :__original_bar]
139
+ sing_methods_with_some_hijacked = sing_methods | [:__original_foo, :__original_bar]
140
+ receiver = (class << object; self; end)
141
+
142
+ Hijacker.should_receive(:guess_instance_methods_from).with(object).and_return(inst_methods_with_some_hijacked)
143
+ Hijacker.should_receive(:guess_singleton_methods_from).with(object).and_return(sing_methods_with_some_hijacked)
144
+
145
+ receiver.should_receive(:undef_method).with(:foo).twice # instance and singleton methods
146
+ receiver.should_receive(:alias_method).with(:foo, :__original_foo).twice
147
+ receiver.should_receive(:undef_method).with(:bar).twice
148
+ receiver.should_receive(:alias_method).with(:bar, :__original_bar).twice
149
+
150
+ Hijacker.restore(object)
151
+ end
152
+ end
153
+
154
+
155
+ end
156
+ end
@@ -14,112 +14,18 @@ class MyClass
14
14
  def bar(a,b)
15
15
  b
16
16
  end
17
- end
18
-
19
- describe Hijacker do
20
-
21
- describe "#spying" do
22
- it 'runs a block spying on a particular object' do
23
- blk = lambda {
24
- MyClass.foo
25
- }
26
- Hijacker.should_receive(:spy).with(MyClass).once.ordered
27
- MyClass.should_receive(:foo).once.ordered
28
- Hijacker.should_receive(:restore).with(MyClass).once.ordered
29
-
30
- Hijacker.spying(MyClass, &blk)
31
- end
32
17
 
33
- it 'raises if no block given' do
34
- expect {
35
- Hijacker.spying(MyClass)
36
- }.to raise_error("No block given")
37
- end
18
+ def baz=(value)
19
+ @value = value
38
20
  end
39
21
 
40
- describe "#spy - #restore" do
41
-
42
- describe "hijacking a Class" do
43
- describe "instance methods" do
44
- before(:each) do
45
- Hijacker.spy(MyClass, :only => :instance_methods)
46
- end
47
- it "registers method calls without arguments" do
48
- Hijacker.should_receive(:register).with(:foo, [], 7, kind_of(MyClass), nil).ordered
49
- MyClass.new.foo.should == 7
50
- end
51
- it "registers method calls with arguments" do
52
- Hijacker.should_receive(:register).with(:bar, [2, "string"], "string", kind_of(MyClass), nil).ordered
53
- MyClass.new.bar(2, "string").should == "string"
54
- end
55
- after(:each) do
56
- Hijacker.restore(MyClass)
57
- end
58
- end
59
- describe "class methods" do
60
- before(:each) do
61
- Hijacker.spy(MyClass)
62
- end
63
- it "registers method calls without arguments" do
64
- Hijacker.should_receive(:register).with(:foo, [], 7, kind_of(Class), nil).ordered
65
- MyClass.foo.should == 7
66
- end
67
- it "registers method calls with arguments" do
68
- Hijacker.should_receive(:register).with(:bar, [2, "string"], "string", kind_of(Class), nil).ordered
69
- MyClass.bar(2, "string").should == "string"
70
- end
71
- after(:each) do
72
- Hijacker.restore(MyClass)
73
- end
74
- end
75
- describe "forbidden classes (are not hijacked)" do
76
- [Array, Hash, String, Fixnum, Float, Numeric, Symbol].each do |forbidden|
77
- it "protects #{forbidden}" do
78
- expect {
79
- Hijacker.spy(forbidden)
80
- }.to raise_error
81
- end
82
- end
83
- end
84
- end
85
- describe "hijacking an object" do
86
- describe "instance methods" do
87
- let(:object) { MyClass.new }
88
-
89
- before(:each) do
90
- def object.my_method
91
- 8
92
- end
93
- def object.my_method_with_args(a,b)
94
- b
95
- end
96
- Hijacker.spy(object)
97
- end
98
- it "registers method calls without arguments" do
99
- Hijacker.should_receive(:register).with(:foo, [], 7, kind_of(MyClass), nil).ordered
100
- Hijacker.should_receive(:register).with(:my_method, [], 8, kind_of(MyClass), nil).ordered
22
+ def method_that_raises
23
+ raise StandardError, "Something went wrong"
24
+ end
101
25
 
102
- object.foo.should == 7
103
- object.my_method.should == 8
104
- end
105
- it "registers method calls with arguments" do
106
- Hijacker.should_receive(:register).with(:bar, [2, "string"], "string", kind_of(MyClass), nil).ordered
107
- Hijacker.should_receive(:register).with(:my_method_with_args, [2, "string"], "string", kind_of(MyClass), nil).ordered
26
+ end unless defined?(MyClass)
108
27
 
109
- object.bar(2, "string").should == "string"
110
- object.my_method_with_args(2, "string").should == "string"
111
- end
112
- it "does not affect other instances of the object's class" do
113
- Hijacker.should_not_receive(:register)
114
- MyClass.new.foo.should == 7
115
- end
116
- after(:each) do
117
- Hijacker.restore(object)
118
- end
119
- end
120
- end
121
-
122
- end
28
+ describe Hijacker do
123
29
 
124
30
  describe "#register" do
125
31
  it 'sends the registered method call to the DRb server' do
@@ -133,18 +39,19 @@ describe Hijacker do
133
39
  {:inspect => "\"string\"", :class => "String"},
134
40
  ],
135
41
  {:inspect => "\"retval\"", :class => "String"},
42
+ nil,
136
43
  {:inspect => "MyClass", :class => "Class"}
137
44
  ]
138
45
 
139
46
  DRbObject.should_receive(:new).with(nil, "druby://localhost:9999").and_return server
140
47
  server.should_receive(:handle).with *expected_args
141
48
 
142
- Hijacker.register(:bar, [2, "string"], "retval", MyClass)
49
+ Hijacker.register(:bar, [2, "string"], "retval", nil, MyClass)
143
50
  end
144
51
  context "when given a particular DRb uri" do
145
52
  it "sends the call to that uri" do
146
53
  DRbObject.should_receive(:new).with(nil, "druby://localhost:1212").and_return mock('DRb server', :handle => true)
147
- Hijacker.register(:bar, [2, "string"], "retval", MyClass, "druby://localhost:1212")
54
+ Hijacker.register(:bar, [2, "string"], "retval", nil, MyClass, "druby://localhost:1212")
148
55
  end
149
56
  end
150
57
  end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 1
7
+ - 2
8
8
  - 0
9
- version: 0.1.0
9
+ version: 0.2.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Josep M. Bach
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-11-21 00:00:00 +01:00
17
+ date: 2010-12-05 00:00:00 +01:00
18
18
  default_executable: hijacker
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -112,6 +112,7 @@ files:
112
112
  - .gitignore
113
113
  - .rspec
114
114
  - .rvmrc
115
+ - Changelog
115
116
  - Gemfile
116
117
  - Gemfile.lock
117
118
  - Guardfile
@@ -121,12 +122,18 @@ files:
121
122
  - hijacker.gemspec
122
123
  - lib/hijacker.rb
123
124
  - lib/hijacker/config.rb
125
+ - lib/hijacker/exceptions.rb
124
126
  - lib/hijacker/handler.rb
125
127
  - lib/hijacker/handlers/logger.rb
128
+ - lib/hijacker/method_definer.rb
129
+ - lib/hijacker/spy.rb
126
130
  - lib/hijacker/version.rb
131
+ - spec/acceptance/acceptance_spec.rb
127
132
  - spec/hijacker/config_spec.rb
128
133
  - spec/hijacker/handler_spec.rb
129
134
  - spec/hijacker/handlers/logger_spec.rb
135
+ - spec/hijacker/method_definer_spec.rb
136
+ - spec/hijacker/spy_spec.rb
130
137
  - spec/hijacker_spec.rb
131
138
  - spec/spec_helper.rb
132
139
  has_rdoc: true
@@ -162,8 +169,11 @@ signing_key:
162
169
  specification_version: 3
163
170
  summary: Spy on your ruby objects and send their activity to a hijacker server anywhere through DRb
164
171
  test_files:
172
+ - spec/acceptance/acceptance_spec.rb
165
173
  - spec/hijacker/config_spec.rb
166
174
  - spec/hijacker/handler_spec.rb
167
175
  - spec/hijacker/handlers/logger_spec.rb
176
+ - spec/hijacker/method_definer_spec.rb
177
+ - spec/hijacker/spy_spec.rb
168
178
  - spec/hijacker_spec.rb
169
179
  - spec/spec_helper.rb