spies 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: bce479180515db93df27766da5049ae9752c616c
4
+ data.tar.gz: 963ef385fabc59bc17ab292b66bd495976b092cf
5
+ SHA512:
6
+ metadata.gz: 88e3a9cd2ba728ef51ed4308add96e58085223d9ea831bd23758ca7fe113afa0a358f3009c0e7347e6f1e4291589724c4e932056660d598f22f4d03a19bb6dff
7
+ data.tar.gz: 8f261270a1589669fbbfcfec3d81dccc3f4eec2f3e63a17a98a0241db1a5387b840ba3c40de17acdfb60dd7731dc7b6d60327955fa66a35b37cd91720851f8c6
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,25 @@
1
+ Encoding:
2
+ Enabled: false
3
+
4
+ Documentation:
5
+ Enabled: false
6
+
7
+ ClassAndModuleChildren:
8
+ Enabled: false
9
+
10
+ ClassLength:
11
+ Enabled: false
12
+
13
+ MethodLength:
14
+ Enabled: false
15
+
16
+ DoubleNegation:
17
+ Enabled: false
18
+
19
+ FrozenStringLiteralComment:
20
+ Enabled: false
21
+
22
+ AllCops:
23
+ Exclude:
24
+ - "bin/**/*"
25
+ - "Guardfile"
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.1
5
+ before_install: gem install bundler -v 1.12.5
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'coveralls', require: false
data/Guardfile ADDED
@@ -0,0 +1,70 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ ## Uncomment and set this to only include directories you want to watch
5
+ # directories %w(app lib config test spec features) \
6
+ # .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
7
+
8
+ ## Note: if you are using the `directories` clause above and you are not
9
+ ## watching the project directory ('.'), then you will want to move
10
+ ## the Guardfile to a watched dir and symlink it back, e.g.
11
+ #
12
+ # $ mkdir config
13
+ # $ mv Guardfile config/
14
+ # $ ln -s config/Guardfile .
15
+ #
16
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17
+
18
+ # Note: The cmd option is now required due to the increasing number of ways
19
+ # rspec may be run, below are examples of the most common uses.
20
+ # * bundler: 'bundle exec rspec'
21
+ # * bundler binstubs: 'bin/rspec'
22
+ # * spring: 'bin/rspec' (This will use spring if running and you have
23
+ # installed the spring binstubs per the docs)
24
+ # * zeus: 'zeus rspec' (requires the server to be started separately)
25
+ # * 'just' rspec: 'rspec'
26
+
27
+ guard :rspec, cmd: "bundle exec rspec" do
28
+ require "guard/rspec/dsl"
29
+ dsl = Guard::RSpec::Dsl.new(self)
30
+
31
+ # Feel free to open issues for suggestions and improvements
32
+
33
+ # RSpec files
34
+ rspec = dsl.rspec
35
+ watch(rspec.spec_helper) { rspec.spec_dir }
36
+ watch(rspec.spec_support) { rspec.spec_dir }
37
+ watch(rspec.spec_files)
38
+
39
+ # Ruby files
40
+ ruby = dsl.ruby
41
+ dsl.watch_spec_files_for(ruby.lib_files)
42
+
43
+ # Rails files
44
+ rails = dsl.rails(view_extensions: %w(erb haml slim))
45
+ dsl.watch_spec_files_for(rails.app_files)
46
+ dsl.watch_spec_files_for(rails.views)
47
+
48
+ watch(rails.controllers) do |m|
49
+ [
50
+ rspec.spec.("routing/#{m[1]}_routing"),
51
+ rspec.spec.("controllers/#{m[1]}_controller"),
52
+ rspec.spec.("acceptance/#{m[1]}")
53
+ ]
54
+ end
55
+
56
+ # Rails config changes
57
+ watch(rails.spec_helper) { rspec.spec_dir }
58
+ watch(rails.routes) { "#{rspec.spec_dir}/routing" }
59
+ watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
60
+
61
+ # Capybara features specs
62
+ watch(rails.view_dirs) { |m| rspec.spec.("features/#{m[1]}") }
63
+ watch(rails.layouts) { |m| rspec.spec.("features/#{m[1]}") }
64
+
65
+ # Turnip features and steps
66
+ watch(%r{^spec/acceptance/(.+)\.feature$})
67
+ watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
68
+ Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
69
+ end
70
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Nick Giancola
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # Ruby Spy [![Build Status](https://travis-ci.org/patbenatar/ruby-spy.svg?branch=master)](https://travis-ci.org/patbenatar/ruby-spy) [![Coverage Status](https://coveralls.io/repos/github/patbenatar/ruby-spy/badge.svg?branch=master)](https://coveralls.io/github/patbenatar/ruby-spy?branch=master)
2
+
3
+ Test spies for Ruby. Why? For fun.
4
+
5
+ ## Installation
6
+
7
+ ```
8
+ gem 'spies', require: 'spy'
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ Spy on all methods of an instance:
14
+
15
+ ```ruby
16
+ spy = Spy.on(my_object)
17
+ spy.some_method
18
+ expect(spy.calls.count).to eq 1
19
+ expect(spy.calls.last.method_name).to eq :some_method
20
+ ```
21
+
22
+ Spy on arguments:
23
+
24
+ ```ruby
25
+ spy = Spy.on(my_object)
26
+
27
+ spy.some_method_with_args('foo', 'bar')
28
+ expect(spy.calls.last.args).to eq %w(foo bar)
29
+
30
+ block = -> {}
31
+ spy.some_method_with_block(&block)
32
+ expect(spy.calls.last.block).to eq block
33
+ ```
34
+
35
+ Spy on one method:
36
+
37
+ ```ruby
38
+ spy = Spy.on(my_object, :some_method)
39
+ spy.some_method
40
+ spy.another_method
41
+ expect(spy.calls.count).to eq 1
42
+ ```
43
+
44
+ Spy on a constant:
45
+
46
+ ```ruby
47
+ spy = Spy.on(SomeClass)
48
+ SomeClass.some_method
49
+ expect(spy.calls.count).to eq 1
50
+ ```
51
+
52
+ Spy on all instances of a class:
53
+
54
+ ```ruby
55
+ spy = Spy.on_all_instances_of(SomeClass)
56
+
57
+ instance_1 = SomeClass.new
58
+ instance_1.some_method
59
+
60
+ instance_2 = SomeClass.new
61
+ instance_2.some_method
62
+
63
+ expect(spy.calls.count).to eq 2
64
+ expect(spy.calls[1].receiver).to eq instance_2
65
+ ```
66
+
67
+ Clean up after yourself:
68
+
69
+ ```ruby
70
+ Spy.clean
71
+ ```
72
+
73
+ Or keep a block clean:
74
+
75
+ ```ruby
76
+ Spy.clean do
77
+ Spy.on(my_object)
78
+ end
79
+ ```
80
+
81
+ Cleaning with RSpec:
82
+
83
+ ```ruby
84
+ before(:each) { Spy.clean }
85
+ ```
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'rubocop/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+ RuboCop::RakeTask.new
7
+
8
+ task default: [:spec, :rubocop]
data/lib/spy/call.rb ADDED
@@ -0,0 +1,10 @@
1
+ class Spy::Call
2
+ attr_reader :receiver, :method_name, :args, :block
3
+
4
+ def initialize(receiver, method_name, args, block)
5
+ @receiver = receiver
6
+ @method_name = method_name
7
+ @args = args
8
+ @block = block
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ class Spy
2
+ VERSION = '0.1.0'.freeze
3
+ end
data/lib/spy.rb ADDED
@@ -0,0 +1,154 @@
1
+ require 'spy/version'
2
+ require 'securerandom'
3
+
4
+ class Spy
5
+ autoload :Call, 'spy/call'
6
+
7
+ THREAD_LOCAL_ACTIVE_SPIES_KEY = 'ruby_spy_active_spies'.freeze
8
+
9
+ def self.active_spies
10
+ Thread.current[THREAD_LOCAL_ACTIVE_SPIES_KEY] ||= []
11
+ end
12
+
13
+ def self.register(spy)
14
+ active_spies << spy unless active_spies.include? spy
15
+ end
16
+
17
+ def self.unregister(spy)
18
+ active_spies.delete(spy)
19
+ end
20
+
21
+ def self.on(obj, method_name = nil)
22
+ spy_with_options(obj, method_name)
23
+ end
24
+
25
+ def self.on_all_instances_of(obj, method_name = nil)
26
+ spy_with_options(obj, method_name, all_instances: true)
27
+ end
28
+
29
+ def self.spy_with_options(obj, method_name, options = {})
30
+ new(obj, options).tap do |spy|
31
+ method_name ? spy.on(method_name) : spy.on_all
32
+ Spy.register(spy)
33
+ end
34
+ end
35
+
36
+ def self.clean
37
+ if block_given?
38
+ outer_active_spies = active_spies
39
+ Thread.current[THREAD_LOCAL_ACTIVE_SPIES_KEY] = nil
40
+
41
+ begin
42
+ yield
43
+ ensure
44
+ clean
45
+ Thread.current[THREAD_LOCAL_ACTIVE_SPIES_KEY] = outer_active_spies
46
+ end
47
+ else
48
+ active_spies.dup.each(&:clean)
49
+ end
50
+ end
51
+
52
+ attr_reader :obj
53
+
54
+ def initialize(obj, all_instances: false)
55
+ @obj = obj
56
+ @calls = []
57
+ @all_instances = all_instances
58
+ @spied_methods_map = {}
59
+ end
60
+
61
+ def on_all
62
+ all_methods.each { |m| spy_on(m) }
63
+ Spy.register(self)
64
+ end
65
+
66
+ def on(method_name)
67
+ spy_on(method_name)
68
+ Spy.register(self)
69
+ end
70
+
71
+ def calls(method_name = nil)
72
+ if method_name
73
+ @calls.select { |c| c.method_name == method_name }
74
+ else
75
+ @calls
76
+ end
77
+ end
78
+
79
+ def clean
80
+ spied_methods_map.keys.each { |m| remove_spy(m) }
81
+ Spy.unregister(self)
82
+ end
83
+
84
+ def dirty?
85
+ spied_methods_map.keys.any?
86
+ end
87
+
88
+ private
89
+
90
+ attr_reader :all_instances, :spied_methods_map
91
+
92
+ def all_methods
93
+ if spying_on_class?
94
+ obj.methods - Class.methods
95
+ elsif spying_on_all_instances?
96
+ obj.instance_methods - Class.instance_methods
97
+ else
98
+ obj.methods - Class.instance_methods
99
+ end
100
+ end
101
+
102
+ def singleton_class
103
+ class << obj; self; end
104
+ end
105
+
106
+ def target_obj
107
+ spying_on_all_instances? ? obj : singleton_class
108
+ end
109
+
110
+ def spy_on(method_name)
111
+ spy = self
112
+ aliased_original_method_name = original_method_name(method_name)
113
+
114
+ target_obj.send(
115
+ :alias_method,
116
+ aliased_original_method_name,
117
+ method_name
118
+ )
119
+
120
+ target_obj.send(:define_method, method_name) do |*args, &block|
121
+ spy.calls << Call.new(self, method_name, args, block)
122
+ send aliased_original_method_name, *args, &block
123
+ end
124
+ end
125
+
126
+ def remove_spy(method_name)
127
+ aliased_original_method_name = original_method_name(method_name)
128
+
129
+ target_obj.send(
130
+ :alias_method,
131
+ method_name,
132
+ aliased_original_method_name
133
+ )
134
+
135
+ target_obj.send(:remove_method, aliased_original_method_name)
136
+
137
+ spied_methods_map.delete method_name
138
+ end
139
+
140
+ def spying_on_class?
141
+ obj.class == Class && !spying_on_all_instances?
142
+ end
143
+
144
+ def spying_on_all_instances?
145
+ !!all_instances
146
+ end
147
+
148
+ def original_method_name(method_name)
149
+ spied_methods_map[method_name] ||= loop do
150
+ name_candidate = "#{method_name}_#{SecureRandom.hex(8)}".to_sym
151
+ break name_candidate unless all_methods.include? name_candidate
152
+ end
153
+ end
154
+ end
data/spies.gemspec ADDED
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'spy/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'spies'
9
+ spec.version = Spy::VERSION
10
+ spec.authors = ['Nick Giancola']
11
+ spec.email = ['nick@philosophie.is']
12
+
13
+ spec.summary = 'Ruby test spies'
14
+ spec.description = 'Ruby test spies'
15
+ spec.homepage = 'https://github.com/patbenatar/ruby-spy'
16
+ spec.license = 'MIT'
17
+
18
+ spec.files = `git ls-files -z`
19
+ .split("\x0")
20
+ .reject { |f| f.match(%r{^(test|spec|features)/}) }
21
+
22
+ spec.bindir = 'exe'
23
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
+ spec.require_paths = ['lib']
25
+
26
+ spec.add_development_dependency 'bundler', '~> 1.12'
27
+ spec.add_development_dependency 'rake', '~> 10.0'
28
+ spec.add_development_dependency 'rspec', '~> 3.0'
29
+ spec.add_development_dependency 'guard-rspec', '~> 4.7', '>= 4.7.2'
30
+ spec.add_development_dependency 'pry-byebug', '~> 3.4'
31
+ spec.add_development_dependency 'rubocop', '~> 0.41.2'
32
+ spec.add_development_dependency 'coveralls'
33
+ end
metadata ADDED
@@ -0,0 +1,161 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spies
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nick Giancola
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-07-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.12'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.12'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: guard-rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '4.7'
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: 4.7.2
65
+ type: :development
66
+ prerelease: false
67
+ version_requirements: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - "~>"
70
+ - !ruby/object:Gem::Version
71
+ version: '4.7'
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 4.7.2
75
+ - !ruby/object:Gem::Dependency
76
+ name: pry-byebug
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '3.4'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '3.4'
89
+ - !ruby/object:Gem::Dependency
90
+ name: rubocop
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: 0.41.2
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: 0.41.2
103
+ - !ruby/object:Gem::Dependency
104
+ name: coveralls
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ description: Ruby test spies
118
+ email:
119
+ - nick@philosophie.is
120
+ executables: []
121
+ extensions: []
122
+ extra_rdoc_files: []
123
+ files:
124
+ - ".gitignore"
125
+ - ".rspec"
126
+ - ".rubocop.yml"
127
+ - ".travis.yml"
128
+ - Gemfile
129
+ - Guardfile
130
+ - LICENSE.txt
131
+ - README.md
132
+ - Rakefile
133
+ - lib/spy.rb
134
+ - lib/spy/call.rb
135
+ - lib/spy/version.rb
136
+ - spies.gemspec
137
+ homepage: https://github.com/patbenatar/ruby-spy
138
+ licenses:
139
+ - MIT
140
+ metadata: {}
141
+ post_install_message:
142
+ rdoc_options: []
143
+ require_paths:
144
+ - lib
145
+ required_ruby_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ required_rubygems_version: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ version: '0'
155
+ requirements: []
156
+ rubyforge_project:
157
+ rubygems_version: 2.5.1
158
+ signing_key:
159
+ specification_version: 4
160
+ summary: Ruby test spies
161
+ test_files: []