Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

first commit

  • Loading branch information...
commit e8bd2c3364d1f1da8f48466abbec1dd260f8eba2 0 parents
Josep M. Bach authored November 20, 2010
4  .gitignore
... ...
@@ -0,0 +1,4 @@
  1
+pkg/*
  2
+*.gem
  3
+.bundle
  4
+*.rbc
2  .rspec
... ...
@@ -0,0 +1,2 @@
  1
+--colour
  2
+--format documentation
1  .rvmrc
... ...
@@ -0,0 +1 @@
  1
+rvm --create use ruby-1.9.2@hijacker
4  Gemfile
... ...
@@ -0,0 +1,4 @@
  1
+source "http://rubygems.org"
  2
+
  3
+# Specify your gem's dependencies in hijacker.gemspec
  4
+gemspec
48  Gemfile.lock
... ...
@@ -0,0 +1,48 @@
  1
+PATH
  2
+  remote: .
  3
+  specs:
  4
+    hijacker (0.0.1)
  5
+      trollop
  6
+
  7
+GEM
  8
+  remote: http://rubygems.org/
  9
+  specs:
  10
+    configuration (1.2.0)
  11
+    diff-lcs (1.1.2)
  12
+    guard (0.2.2)
  13
+      open_gem (~> 1.4.2)
  14
+      thor (~> 0.14.3)
  15
+    guard-rspec (0.1.8)
  16
+      guard (>= 0.2.0)
  17
+    launchy (0.3.7)
  18
+      configuration (>= 0.0.5)
  19
+      rake (>= 0.8.1)
  20
+    open_gem (1.4.2)
  21
+      launchy (~> 0.3.5)
  22
+    rake (0.8.7)
  23
+    rspec (2.1.0)
  24
+      rspec-core (~> 2.1.0)
  25
+      rspec-expectations (~> 2.1.0)
  26
+      rspec-mocks (~> 2.1.0)
  27
+    rspec-core (2.1.0)
  28
+    rspec-expectations (2.1.0)
  29
+      diff-lcs (~> 1.1.2)
  30
+    rspec-mocks (2.1.0)
  31
+    simplecov (0.3.7)
  32
+      simplecov-html (>= 0.3.7)
  33
+    simplecov-html (0.3.9)
  34
+    thor (0.14.4)
  35
+    trollop (1.16.2)
  36
+
  37
+PLATFORMS
  38
+  java
  39
+  ruby
  40
+
  41
+DEPENDENCIES
  42
+  bundler (~> 1.0.7)
  43
+  guard
  44
+  guard-rspec
  45
+  hijacker!
  46
+  rspec (~> 2.1.0)
  47
+  simplecov
  48
+  trollop
8  Guardfile
... ...
@@ -0,0 +1,8 @@
  1
+# A sample Guardfile
  2
+# More info at http://github.com/guard/guard#readme
  3
+
  4
+guard 'rspec', :version => 2 do
  5
+  watch('^spec/(.*)_spec.rb')
  6
+  watch('^lib/(.*)\.rb')                              { |m| "spec/#{m[1]}_spec.rb" }
  7
+  watch('^spec/spec_helper.rb')                       { "spec" }
  8
+end
10  Rakefile
... ...
@@ -0,0 +1,10 @@
  1
+require 'bundler'
  2
+Bundler::GemHelper.install_tasks
  3
+
  4
+require 'rspec/core'
  5
+require 'rspec/core/rake_task'
  6
+RSpec::Core::RakeTask.new(:spec) do |spec|
  7
+  spec.pattern = FileList['spec/**/*_spec.rb']
  8
+end
  9
+
  10
+task :default => :spec
114  Readme.md
Source Rendered
... ...
@@ -0,0 +1,114 @@
  1
+#hijacker
  2
+
  3
+A little gem that hijacks any ruby object and broadcasts all its activity
  4
+to a particular hijacker server. Useful for logging and those awfully hardcore
  5
+debugging afternoons! There might be other uses to it, for sure. Just be
  6
+creative :)
  7
+
  8
+Hijacker is tested with Ruby 1.8.7, 1.9.2, JRuby 1.5.3 and Rubinius 1.1.
  9
+
  10
+##Install and configure
  11
+
  12
+In your Gemfile:
  13
+
  14
+    gem 'hijacker'
  15
+
  16
+If you are using Rails, you might want to put this configuration snippet in an
  17
+initializer or something (you can always put it in any other part of the code
  18
+otherwise, as long as it's before hijacking any object):
  19
+
  20
+    Hijacker.configure do
  21
+      uri '<YOUR HIJACKER SERVER URI>'
  22
+    end
  23
+
  24
+And that's it! Oooor not. You have to spawn your server. In the command line:
  25
+
  26
+    hijacker
  27
+
  28
+And it will output the URI for this server. *Note this* and pass it to your
  29
+configuration block!
  30
+
  31
+Some options you can pass to the server command:
  32
+
  33
+    hijacker --without-timestamps (don't show the timestamps)
  34
+    hijacker --without-classes (don't show the object classes)
  35
+    hijacker --port 1234 (spawn the server in port 1234 rather than 8787)
  36
+
  37
+##Ok, and now for some hijacking action!
  38
+
  39
+    require 'hijacker'  # You don't have to when using Bundler :)
  40
+   
  41
+    class MyClass
  42
+      def foo(bar, baz)
  43
+        bar + baz 
  44
+      end
  45
+    end
  46
+
  47
+    some_object = Object.new
  48
+
  49
+    # These are the important lines:
  50
+
  51
+    Hijacker.spy(some_object)
  52
+    Hijacker.spy(MyClass)
  53
+
  54
+    instance = MyClass.new
  55
+    instance.foo(3, 4)
  56
+
  57
+Run this code and, if you look at the server output, you'll see nothing less
  58
+than...
  59
+
  60
+    <a nice timestamp> MyClass (Class) received :new and returned #<MyClass:0x000000874> (MyClass)
  61
+    <a nice timestamp> #<MyClass:0x000000874> (MyClass) received :foo with 3
  62
+    (Fixnum), 4 (Fixnum) and returned 7
  63
+
  64
+If you want to un-hijack any object, just call #restore:
  65
+
  66
+    Hijacker.restore(MyClass)
  67
+    Hijacker.restore(some_object)
  68
+
  69
+If you don't want to have to remember every hijacked object you have to call
  70
+restore on it, you can just spy a particular object within the duration of a block:
  71
+
  72
+    Hijacker.spying(MyClass) do
  73
+      # inside this block, MyClass will be spied
  74
+    end
  75
+    # here not anymore
  76
+
  77
+Awesome! You can fine-tune your spying, for example by only spying on instance
  78
+methods or singleton methods only:
  79
+
  80
+    Hijacker.spy(MyClass, :only => :instance_methods) # or :singleton_methods
  81
+
  82
+And, last but not least... you can specify a *particular hijacker server* for
  83
+a *particular object* you are spying on!
  84
+
  85
+    # All activity on MyClass and its instances will
  86
+    # be sent to druby://localhost:9999
  87
+    Hijacker.spy(MyClass, :uri => 'druby://localhost:9999')
  88
+
  89
+    # But for example, the activity of some_object will
  90
+    # be sent to the default uri specified in the configuration
  91
+    # back earlier (remember?)
  92
+    Hijacker.spy(some_object)
  93
+
  94
+Of course, you can specify a particular server uri for a block, with #spying:
  95
+
  96
+    Hijacker.spying(foo_object, :uri => 'druby://localhost:1234') do
  97
+      # all the activity of foo_object inside this block
  98
+      # will be sent to the hijacker server on druby://localhost:1234
  99
+    end
  100
+   
  101
+##Note on Patches/Pull Requests
  102
+ 
  103
+* Fork the project.
  104
+* Make your feature addition or bug fix.
  105
+* Add specs for it. This is important so I don't break it in a
  106
+  future version unintentionally.
  107
+* Commit, do not mess with rakefile, version, or history.
  108
+  If you want to have your own version, that is fine but bump version
  109
+  in a commit by itself I can ignore when I pull.
  110
+* Send me a pull request. Bonus points for topic branches.
  111
+
  112
+## Copyright
  113
+
  114
+Copyright (c) 2010 Josep M. Bach. See LICENSE for details.
50  bin/hijacker
... ...
@@ -0,0 +1,50 @@
  1
+#!/usr/bin/env ruby -w
  2
+# hijacker server
  3
+$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
  4
+
  5
+require 'hijacker'
  6
+
  7
+opts = Trollop::options do
  8
+  version "hijacker 0.0.1 (c) 2010 Josep M. Bach"
  9
+  banner <<-EOS
  10
+  Hijacker server listens for reports by hijackers spying on ruby objects.
  11
+
  12
+  Usage:
  13
+        hijacker [options]
  14
+  where [options] are:
  15
+EOS
  16
+  opt :without_classes, "Don't show classes of objects"
  17
+  opt :without_timestamps, "Don't show timestamps"
  18
+  opt :port, "DRb port to use (default is 8787)", :default => 8787
  19
+end
  20
+
  21
+begin
  22
+  raise unless opts[:port].to_i > 0 && opts[:port].to_i < 9999
  23
+rescue
  24
+  Trollop::die :port, "must be a valid number between 0 and 9999"
  25
+end
  26
+
  27
+DRB_URI="druby://localhost:#{opts[:port]}"
  28
+
  29
+# Start up the DRb service
  30
+DRb.start_service DRB_URI, Hijacker::Logger.new(opts)
  31
+
  32
+ANSI = Hijacker::Logger::ANSI
  33
+# We need the uri of the service to connect a client
  34
+welcome = []
  35
+welcome << ANSI[:BOLD] + "hijacker server"
  36
+welcome << "listening on"
  37
+welcome << ANSI[:BOLD] + DRb.uri + ANSI[:RESET]
  38
+puts welcome.join("#{ANSI[:RESET]} ") + "\n"
  39
+
  40
+# We need the uri of the service to connect a client
  41
+instructions = "Put this code in the configuration of your ruby program #{ANSI[:BOLD]}before any call to Hijacker#{ANSI[:RESET]}:\n\n"
  42
+instructions += "\t" + "Hijacker.config do\n"
  43
+instructions += "\t" + "  uri '#{DRb.uri}'\n"
  44
+instructions += "\t" + "end\n\n"
  45
+puts instructions
  46
+instructions = "Or optionally attach a particular hijacked object to this server adding :uri => '#{DRb.uri}' when calling Hijacker's :spy or :spying method.\n\n"
  47
+puts instructions
  48
+
  49
+# wait for the DRb service to finish before exiting
  50
+DRb.thread.join
30  hijacker.gemspec
... ...
@@ -0,0 +1,30 @@
  1
+# -*- encoding: utf-8 -*-
  2
+$:.push File.expand_path("../lib", __FILE__)
  3
+require "hijacker/version"
  4
+
  5
+Gem::Specification.new do |s|
  6
+  s.name        = "hijacker"
  7
+  s.version     = Hijacker::VERSION
  8
+  s.platform    = Gem::Platform::RUBY
  9
+  s.authors     = ["Josep M. Bach"]
  10
+  s.email       = ["josep.m.bach@gmail.com"]
  11
+  s.homepage    = "http://github.com/txus/hijacker"
  12
+  s.summary     = %q{Spy on your ruby objects and send their activity to a hijacker server anywhere through DRb}
  13
+  s.description = %q{Spy on your ruby objects and send their activity to a hijacker server anywhere through DRb}
  14
+
  15
+  s.rubyforge_project = "hijacker"
  16
+
  17
+  s.add_runtime_dependency 'trollop'
  18
+  s.default_executable = "hijacker"
  19
+
  20
+  s.add_development_dependency 'bundler', '~> 1.0.7'
  21
+  s.add_development_dependency 'rspec',   '~> 2.1.0'
  22
+  s.add_development_dependency 'guard'
  23
+  s.add_development_dependency 'guard-rspec'
  24
+  s.add_development_dependency "simplecov"
  25
+
  26
+  s.files         = `git ls-files`.split("\n")
  27
+  s.test_files    = `git ls-files -- {test,spec,features}/*`.split("\n")
  28
+  s.executables   = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
  29
+  s.require_paths = ["lib"]
  30
+end
116  lib/hijacker.rb
... ...
@@ -0,0 +1,116 @@
  1
+require 'drb'
  2
+require 'trollop'
  3
+require 'hijacker/config'
  4
+require 'hijacker/logger'
  5
+
  6
+module Hijacker
  7
+
  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
+  class << self
  13
+
  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.dup, #{uri.inspect}
  44
+              end
  45
+            end
  46
+EOS
  47
+      end unless options[:only] == :singleton_methods
  48
+
  49
+      receiver = (class << object; self; end)
  50
+      sing_methods.each do |met|
  51
+          puts "Defining #{met}"
  52
+          receiver.send(:alias_method, :"__original_#{met}", :"#{met}")
  53
+          receiver.send(:undef_method, :"#{met}")
  54
+          receiver.class_eval <<EOS
  55
+            def #{met}(*args, &blk)
  56
+              __original_#{met}(*args,&blk).tap do |retval|
  57
+                Hijacker.register :#{met}, args, retval, self.dup, #{uri.inspect}
  58
+              end
  59
+            end
  60
+EOS
  61
+      end unless options[:only] == :instance_methods
  62
+
  63
+    end
  64
+
  65
+    def restore(object)
  66
+      receiver = if object.is_a?(Class)
  67
+        object
  68
+      else
  69
+        (class << object; self; end)
  70
+      end
  71
+      guess_instance_methods_from(object).select{|m| m =~ /__original_/}.each do |met|
  72
+        met = met.to_s.gsub!("__original_", "")
  73
+        receiver.send(:undef_method, :"#{met}")
  74
+        receiver.send(:alias_method, :"#{met}", :"__original_#{met}")
  75
+      end
  76
+
  77
+      receiver = (class << object; self; end)
  78
+      guess_class_methods_from(object).select{|m| m =~ /__original_/}.each do |met|
  79
+        met = met.to_s.gsub!("__original_", "")
  80
+        receiver.send(:undef_method, :"#{met}")
  81
+        receiver.send(:alias_method, :"#{met}", :"__original_#{met}")
  82
+      end
  83
+    end
  84
+
  85
+    def register(method, args, retval, object, uri = nil)
  86
+      args.map! do |arg|
  87
+        {:inspect => arg.inspect, :class => arg.class.name}
  88
+      end
  89
+      retval = {:inspect => retval.inspect, :class => retval.class.name}
  90
+      object = {:inspect => object.inspect, :class => object.class.name}
  91
+
  92
+      server = DRbObject.new nil, (uri || self.drb_uri)
  93
+      server.log method, args, retval, object
  94
+    end
  95
+
  96
+    private
  97
+
  98
+    def guess_instance_methods_from(object)
  99
+      if object.is_a?(Class)
  100
+        object.instance_methods
  101
+      else
  102
+        object.methods
  103
+      end
  104
+    end
  105
+
  106
+    def guess_class_methods_from(object)
  107
+      if object.is_a?(Class)
  108
+        object.methods
  109
+      else
  110
+        []
  111
+      end
  112
+    end
  113
+
  114
+  end
  115
+
  116
+end
19  lib/hijacker/config.rb
... ...
@@ -0,0 +1,19 @@
  1
+module Hijacker
  2
+  class << self
  3
+    def configure(&block)
  4
+      self.instance_eval(&block)
  5
+    end
  6
+
  7
+    def uri(drb)
  8
+      @@drb_uri = drb
  9
+    end
  10
+
  11
+    def drb_uri
  12
+      begin
  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."
  16
+      end
  17
+    end
  18
+  end
  19
+end
53  lib/hijacker/logger.rb
... ...
@@ -0,0 +1,53 @@
  1
+module Hijacker
  2
+  class Logger
  3
+
  4
+    ANSI = {:RESET=>"\e[0m", :BOLD=>"\e[1m", :UNDERLINE=>"\e[4m",
  5
+            :LGRAY=>"\e[0;37m", :GRAY=>"\e[1;30m",
  6
+            :RED=>"\e[31m",
  7
+            :GREEN=>"\e[32m", :LGREEN=>"\e[1;32m",
  8
+            :YELLOW=>"\e[33m",
  9
+            :BLUE=>"\e[34m", :LBLUE=>"\e[1;34m",
  10
+            :PURPLE=>"\e[35m", :LPURPLE=>"\e[1;35m",
  11
+            :CYAN=>"\e[36m", :LCYAN=>"\e[1;36m",
  12
+            :WHITE=>"\e[37m"}
  13
+    
  14
+    # Make dRuby send Logger instances as dRuby references,
  15
+    # not copies.
  16
+    include DRb::DRbUndumped
  17
+
  18
+    attr_reader :opts
  19
+
  20
+    def initialize(opts)
  21
+      @opts = opts
  22
+    end
  23
+
  24
+    def log(method, args, retval, object)
  25
+      out = []
  26
+      out << ANSI[:BOLD] + ANSI[:UNDERLINE] + "#{Time.now}" unless opts[:without_timestamps]
  27
+      out << ANSI[:CYAN] + object[:inspect]
  28
+      out << ANSI[:LCYAN] + "(#{object[:class]})" unless opts[:without_classes]
  29
+      out << "received"
  30
+      out << ANSI[:RED] + ":#{method}"
  31
+      unless args.empty?
  32
+        out << "with"
  33
+        out << args.map do |arg|
  34
+          ANSI[:GREEN] + arg[:inspect] + ANSI[:LGREEN] + (opts[:without_classes] ? "" : " (#{arg[:class]})") +
  35
+          ANSI[:RESET]
  36
+        end.join(', ')
  37
+      end
  38
+      out << "and returned"
  39
+      out << ANSI[:BLUE] + retval[:inspect]
  40
+      out << ANSI[:LBLUE] + "(#{retval[:class]})" unless opts[:without_classes]
  41
+      out << ANSI[:RESET] + "\n"
  42
+      stdout.print out.join("#{ANSI[:RESET]} ")
  43
+    end
  44
+
  45
+    private
  46
+
  47
+    def stdout
  48
+      $stdout
  49
+    end
  50
+
  51
+  end
  52
+
  53
+end
3  lib/hijacker/version.rb
... ...
@@ -0,0 +1,3 @@
  1
+module Hijacker
  2
+  VERSION = "0.0.1"
  3
+end
14  spec/hijacker/config_spec.rb
... ...
@@ -0,0 +1,14 @@
  1
+require 'spec_helper'
  2
+
  3
+describe Hijacker, "configuration" do
  4
+
  5
+  describe "#configure" do
  6
+    it 'accepts a block with the \'uri\' configuration option' do
  7
+      Hijacker.configure do
  8
+        uri 'druby://localhost:8787'
  9
+      end
  10
+      Hijacker.drb_uri.should == 'druby://localhost:8787'
  11
+    end
  12
+  end
  13
+  
  14
+end
81  spec/hijacker/logger_spec.rb
... ...
@@ -0,0 +1,81 @@
  1
+require 'spec_helper'
  2
+
  3
+module Hijacker
  4
+  describe Logger do
  5
+
  6
+    subject { Logger.new({:my => :option}) }
  7
+
  8
+    it "initializes with options" do
  9
+      subject.opts.should == {:my => :option}
  10
+    end
  11
+
  12
+    describe "#log" do
  13
+
  14
+      let(:args) do
  15
+        [:bar,
  16
+         [
  17
+          {:inspect => "2", :class => "Fixnum"},
  18
+          {:inspect => "\"string\"", :class => "String"},
  19
+         ],
  20
+         {:inspect => "\"retval\"", :class => "String"},
  21
+         {:inspect => "MyClass", :class => "Class"}]
  22
+      end
  23
+
  24
+      it 'prints the received args' do
  25
+        out = StringIO.new
  26
+        subject.stub(:stdout).and_return out
  27
+
  28
+        Time.stub(:now).and_return Time.parse('2010-11-20')
  29
+
  30
+        subject.log(*args)
  31
+
  32
+        ["00:00:00 +0100",
  33
+         "MyClass",
  34
+         "(Class)",
  35
+         "received",
  36
+         ":bar",
  37
+         "with",
  38
+         "2",
  39
+         "(Fixnum)",
  40
+         "\"string\"",
  41
+         "(String)",
  42
+         "and returned",
  43
+         "\"retval\"",
  44
+         "(String)"].each do |str|
  45
+          out.string.should include(str)
  46
+         end
  47
+      end
  48
+      context "when given :without_timestamps" do
  49
+        it 'discards the timestamps' do
  50
+          logger = Logger.new({:without_timestamps => true})
  51
+
  52
+          out = StringIO.new
  53
+          logger.stub(:stdout).and_return out
  54
+
  55
+          Time.stub(:now).and_return Time.parse('2010-11-20')
  56
+
  57
+          logger.log(*args)
  58
+
  59
+          out.string.should_not include("2010-11-20")
  60
+        end
  61
+      end
  62
+      context "when given :without_classes" do
  63
+        it 'discards the classes' do
  64
+          logger = Logger.new({:without_classes => true})
  65
+
  66
+          out = StringIO.new
  67
+          logger.stub(:stdout).and_return out
  68
+
  69
+          Time.stub(:now).and_return Time.parse('2010-11-20')
  70
+
  71
+          logger.log(*args)
  72
+
  73
+          ["(Class)", "(Fixnum)", "(String)"].each do |str|
  74
+            out.string.should_not include(str)
  75
+          end
  76
+        end
  77
+      end
  78
+    end
  79
+    
  80
+  end
  81
+end
152  spec/hijacker_spec.rb
... ...
@@ -0,0 +1,152 @@
  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
+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
+
  33
+    it 'raises if no block given' do
  34
+      expect {
  35
+        Hijacker.spying(MyClass)
  36
+      }.to raise_error("No block given")
  37
+    end
  38
+  end
  39
+
  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
  101
+
  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
  108
+
  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
  123
+
  124
+  describe "#register" do
  125
+    it 'sends the registered method call to the DRb server' do
  126
+      server = mock('DRb server') 
  127
+
  128
+      Hijacker.stub(:drb_uri).and_return "druby://localhost:9999"
  129
+
  130
+      expected_args = [:bar,
  131
+                       [
  132
+                        {:inspect => "2", :class => "Fixnum"},
  133
+                        {:inspect => "\"string\"", :class => "String"},
  134
+                       ],
  135
+                       {:inspect => "\"retval\"", :class => "String"},
  136
+                       {:inspect => "MyClass", :class => "Class"}
  137
+                      ]
  138
+
  139
+      DRbObject.should_receive(:new).with(nil, "druby://localhost:9999").and_return server
  140
+      server.should_receive(:log).with *expected_args
  141
+
  142
+      Hijacker.register(:bar, [2, "string"], "retval", MyClass) 
  143
+    end
  144
+    context "when given a particular DRb uri" do
  145
+      it "sends the call to that uri" do
  146
+        DRbObject.should_receive(:new).with(nil, "druby://localhost:1212").and_return mock('DRb server', :log => true)
  147
+        Hijacker.register(:bar, [2, "string"], "retval", MyClass, "druby://localhost:1212") 
  148
+      end
  149
+    end
  150
+  end
  151
+
  152
+end
17  spec/spec_helper.rb
... ...
@@ -0,0 +1,17 @@
  1
+require 'bundler'
  2
+begin
  3
+  Bundler.setup(:default, :development)
  4
+rescue Bundler::BundlerError => e
  5
+  $stderr.puts e.message
  6
+  $stderr.puts "Run `bundle install` to install missing gems"
  7
+  exit e.status_code
  8
+end
  9
+$LOAD_PATH.unshift(File.dirname(__FILE__))
  10
+$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
  11
+
  12
+require 'hijacker'
  13
+require 'rspec'
  14
+
  15
+# Requires supporting files with custom matchers and macros, etc,
  16
+# in ./support/ and its subdirectories.
  17
+Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}

0 notes on commit e8bd2c3

Please sign in to comment.
Something went wrong with that request. Please try again.