hijacker 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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