rspec-memory 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0a4c2e285080325137845f068d8b7a94e03b495b7290537d3642233122ebfc01
4
+ data.tar.gz: 84b2fd6ff30ed7759d0691c556f771b0732a87687fcd518dc066d6a6c793be2e
5
+ SHA512:
6
+ metadata.gz: 3bfaf9c3914b29f48285dc720f7bc30ad854ebd8ac765de34678488d7064ba613c76fbdaafd1af0810d24ef6f28c76306b90e71d5b21d77c55acbabc5e800b60
7
+ data.tar.gz: 1df26e162deaad5d6e32620d6940930fa0bc490e263adf2555e831ce645b25a3c26e3360d31d24a4a7d7ddcc91f085b0b8b88ea07f520ff82c2929cb6b30d2c3
data/.editorconfig ADDED
@@ -0,0 +1,6 @@
1
+ root = true
2
+
3
+ [*]
4
+ indent_style = tab
5
+ indent_size = 2
6
+
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
13
+ .covered.db
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --warnings
3
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,20 @@
1
+ language: ruby
2
+ dist: xenial
3
+ cache: bundler
4
+
5
+ matrix:
6
+ include:
7
+ - rvm: 2.3
8
+ - rvm: 2.4
9
+ - rvm: 2.5
10
+ - rvm: 2.6
11
+ - rvm: 2.6
12
+ env: COVERAGE=BriefSummary,Coveralls
13
+ - rvm: ruby-head
14
+ - rvm: jruby-head
15
+ env: JRUBY_OPTS="--debug -X+O"
16
+ - rvm: truffleruby
17
+ allow_failures:
18
+ - rvm: ruby-head
19
+ - rvm: jruby-head
20
+ - rvm: truffleruby
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rspec-memory.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # RSpec::Memory
2
+
3
+ Make assertions about memory usage.
4
+
5
+ [![Build Status](https://travis-ci.com/socketry/rspec-memory.svg?branch=master)](https://travis-ci.com/socketry/rspec-memory)
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'rspec-memory'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install rspec-memory
22
+
23
+ Finally, add this require statement to the top of `spec/spec_helper.rb`
24
+
25
+ ```ruby
26
+ require 'async/rspec'
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ Allocating large amounts of objects can lead to memery problems. `RSpec::Memory` adds a `limit_allocations` matcher, which tracks the number of allocations and memory size for each object type and allows you to specify expected limits.
32
+
33
+ ```ruby
34
+ RSpec.describe "memory allocations" do
35
+ include_context RSpec::Memory
36
+
37
+ it "limits allocation counts" do
38
+ expect do
39
+ 6.times{String.new}
40
+ end.to limit_allocations(String => 10) # 10 strings can be allocated
41
+ end
42
+
43
+ it "limits allocation counts (hash)" do
44
+ expect do
45
+ 6.times{String.new}
46
+ end.to limit_allocations(String => {count: 10}) # 10 strings can be allocated
47
+ end
48
+
49
+ it "limits allocation size" do
50
+ expect do
51
+ 6.times{String.new("foo")}
52
+ end.to limit_allocations(String => {size: 1024}) # 1 KB of strings can be allocated
53
+ end
54
+ end
55
+ ```
56
+
57
+ ## Contributing
58
+
59
+ 1. Fork it
60
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
61
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
62
+ 4. Push to the branch (`git push origin my-new-feature`)
63
+ 5. Create new Pull Request
64
+
65
+ ## License
66
+
67
+ Released under the MIT license.
68
+
69
+ Copyright, 2017, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
70
+
71
+ Permission is hereby granted, free of charge, to any person obtaining a copy
72
+ of this software and associated documentation files (the "Software"), to deal
73
+ in the Software without restriction, including without limitation the rights
74
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
75
+ copies of the Software, and to permit persons to whom the Software is
76
+ furnished to do so, subject to the following conditions:
77
+
78
+ The above copyright notice and this permission notice shall be included in
79
+ all copies or substantial portions of the Software.
80
+
81
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
82
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
83
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
84
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
85
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
86
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
87
+ THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:test)
5
+
6
+ task :default => :test
@@ -0,0 +1,25 @@
1
+ # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'memory/matchers/limit_allocations'
22
+
23
+ RSpec.shared_context RSpec::Memory do
24
+ include RSpec::Memory::Matchers
25
+ end
@@ -0,0 +1,113 @@
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ # Copyright, 2018, by Janko Marohnić.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+
22
+ require_relative '../trace'
23
+
24
+ require 'rspec/expectations'
25
+
26
+ module RSpec
27
+ module Memory
28
+ module Matchers
29
+ class LimitAllocations
30
+ include RSpec::Matchers::Composable
31
+
32
+ def initialize(allocations = {}, count: nil, size: nil)
33
+ @count = count
34
+ @size = size
35
+
36
+ @allocations = {}
37
+ @errors = []
38
+
39
+ allocations.each do |klass, count|
40
+ self.of(klass, count: count)
41
+ end
42
+ end
43
+
44
+ def supports_block_expectations?
45
+ true
46
+ end
47
+
48
+ def of(klass, **limits)
49
+ @allocations[klass] = limits
50
+
51
+ return self
52
+ end
53
+
54
+ private def check(value, limit)
55
+ case limit
56
+ when Range
57
+ unless limit.include? value
58
+ yield "expected within #{limit}"
59
+ end
60
+ when Integer
61
+ unless value == limit
62
+ yield "expected exactly #{limit}"
63
+ end
64
+ end
65
+ end
66
+
67
+ def matches?(given_proc)
68
+ return true unless trace = Trace.capture(@allocations.keys, &given_proc)
69
+
70
+ if @count or @size
71
+ # If the spec specifies a total limit, we have a limit which we can enforce which takes all allocations into account:
72
+ total = trace.total
73
+
74
+ check(total.count, @count) do |expected|
75
+ @errors << "allocated #{total.count} instances, #{total.size} bytes, #{expected} instances"
76
+ end if @count
77
+
78
+ check(total.size, @size) do |expected|
79
+ @errors << "allocated #{total.count} instances, #{total.size} bytes, #{expected} bytes"
80
+ end if @size
81
+ else
82
+ # Otherwise unspecified allocations are considered an error:
83
+ trace.ignored.each do |klass, allocation|
84
+ @errors << "allocated #{allocation.count} #{klass} instances, #{allocation.size} bytes, but it was not specified"
85
+ end
86
+ end
87
+
88
+ trace.allocated.each do |klass, allocation|
89
+ next unless acceptable = @allocations[klass]
90
+
91
+ check(allocation.count, acceptable[:count]) do |expected|
92
+ @errors << "allocated #{allocation.count} #{klass} instances, #{allocation.size} bytes, #{expected} instances"
93
+ end
94
+
95
+ check(allocation.size, acceptable[:size]) do |expected|
96
+ @errors << "allocated #{allocation.count} #{klass} instances, #{allocation.size} bytes, #{expected} bytes"
97
+ end
98
+ end
99
+
100
+ return @errors.empty?
101
+ end
102
+
103
+ def failure_message
104
+ "exceeded allocation limit: #{@errors.join(', ')}"
105
+ end
106
+ end
107
+
108
+ def limit_allocations(*args)
109
+ LimitAllocations.new(*args)
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,138 @@
1
+ # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'objspace'
22
+
23
+ module RSpec
24
+ module Memory
25
+ Allocation = Struct.new(:count, :size) do
26
+ SLOT_SIZE = 40
27
+
28
+ def << object
29
+ self.count += 1
30
+
31
+ # We don't want to force specs to take the slot size into account.
32
+ self.size += ObjectSpace.memsize_of(object) - SLOT_SIZE
33
+ end
34
+
35
+ def self.default_hash
36
+ Hash.new{|h,k| h[k] = Allocation.new(0, 0)}
37
+ end
38
+ end
39
+
40
+ class Trace
41
+ def self.supported?
42
+ # There are issues on truffleruby-1.0.0rc9
43
+ return false if RUBY_ENGINE == "truffleruby"
44
+
45
+ ObjectSpace.respond_to?(:trace_object_allocations)
46
+ end
47
+
48
+ if supported?
49
+ def self.capture(*args, &block)
50
+ self.new(*args).tap do |trace|
51
+ trace.capture(&block)
52
+ end
53
+ end
54
+ else
55
+ def self.capture(*args, &block)
56
+ yield
57
+
58
+ return nil
59
+ end
60
+ end
61
+
62
+ def initialize(klasses)
63
+ @klasses = klasses
64
+
65
+ @allocated = Allocation.default_hash
66
+ @retained = Allocation.default_hash
67
+
68
+ @ignored = Allocation.default_hash
69
+
70
+ @total = Allocation.new(0, 0)
71
+ end
72
+
73
+ attr :allocated
74
+ attr :retained
75
+
76
+ attr :ignored
77
+
78
+ attr :total
79
+
80
+ def current_objects(generation)
81
+ allocations = []
82
+
83
+ ObjectSpace.each_object do |object|
84
+ if ObjectSpace.allocation_generation(object) == generation
85
+ allocations << object
86
+ end
87
+ end
88
+
89
+ return allocations
90
+ end
91
+
92
+ def find_base(object)
93
+ @klasses.find{|klass| object.is_a? klass}
94
+ end
95
+
96
+ def capture(&block)
97
+ GC.start
98
+
99
+ begin
100
+ GC.disable
101
+
102
+ generation = GC.count
103
+ ObjectSpace.trace_object_allocations(&block)
104
+
105
+ allocated = current_objects(generation)
106
+ ensure
107
+ GC.enable
108
+ end
109
+
110
+ GC.start
111
+ retained = current_objects(generation)
112
+
113
+ # All allocated objects, including those freed in the last GC:
114
+ allocated.each do |object|
115
+ if klass = find_base(object)
116
+ @allocated[klass] << object
117
+ else
118
+ # If the user specified classes, but we can't pin this allocation to a specific class, we issue a warning.
119
+ if @klasses.any?
120
+ warn "Ignoring allocation of #{object.class} at #{ObjectSpace.allocation_sourcefile(object)}:#{ObjectSpace.allocation_sourceline(object)}"
121
+ end
122
+
123
+ @ignored[object.class] << object
124
+ end
125
+
126
+ @total << object
127
+ end
128
+
129
+ # Retained objects are still alive after a final GC:
130
+ retained.each do |object|
131
+ if klass = find_base(object)
132
+ @retained[klass] << object
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,25 @@
1
+ # Copyright, 2019, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module RSpec
22
+ module Memory
23
+ VERSION = "1.0.0"
24
+ end
25
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ require_relative 'lib/rspec/memory/version'
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = "rspec-memory"
6
+ spec.version = RSpec::Memory::VERSION
7
+ spec.authors = ["Samuel Williams"]
8
+ spec.email = ["samuel.williams@oriontransfer.co.nz"]
9
+
10
+ spec.summary = "Matches for checking memory allocations."
11
+ spec.homepage = "https://github.com/socketry/rspec-memory"
12
+
13
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
14
+ f.match(%r{^(test|spec|features)/})
15
+ end
16
+
17
+ spec.require_paths = ["lib"]
18
+
19
+ spec.add_dependency "rspec", "~> 3.0"
20
+
21
+ spec.add_development_dependency "covered"
22
+ spec.add_development_dependency "bundler"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rspec-memory
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Samuel Williams
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-07-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: covered
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ description:
70
+ email:
71
+ - samuel.williams@oriontransfer.co.nz
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".editorconfig"
77
+ - ".gitignore"
78
+ - ".rspec"
79
+ - ".travis.yml"
80
+ - Gemfile
81
+ - README.md
82
+ - Rakefile
83
+ - lib/rspec/memory.rb
84
+ - lib/rspec/memory/matchers/limit_allocations.rb
85
+ - lib/rspec/memory/trace.rb
86
+ - lib/rspec/memory/version.rb
87
+ - rspec-memory.gemspec
88
+ homepage: https://github.com/socketry/rspec-memory
89
+ licenses: []
90
+ metadata: {}
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubygems_version: 3.0.3
107
+ signing_key:
108
+ specification_version: 4
109
+ summary: Matches for checking memory allocations.
110
+ test_files: []