pax_global_header00006660000000000000000000000064136301460740014516gustar00rootroot0000000000000052 comment=7333c4137bc7296a791fe3ada099c61f150a5687 ruby-rspec-memory-1.0.2/000077500000000000000000000000001363014607400151175ustar00rootroot00000000000000ruby-rspec-memory-1.0.2/.editorconfig000066400000000000000000000000651363014607400175750ustar00rootroot00000000000000root = true [*] indent_style = tab indent_size = 2 ruby-rspec-memory-1.0.2/.gitignore000066400000000000000000000002131363014607400171030ustar00rootroot00000000000000/.bundle/ /.yardoc /Gemfile.lock /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ # rspec failure tracking .rspec_status .covered.db ruby-rspec-memory-1.0.2/.rspec000066400000000000000000000000701363014607400162310ustar00rootroot00000000000000--format documentation --warnings --require spec_helper ruby-rspec-memory-1.0.2/.travis.yml000066400000000000000000000006161363014607400172330ustar00rootroot00000000000000language: ruby dist: xenial cache: bundler matrix: include: - rvm: 2.3 - rvm: 2.4 - rvm: 2.5 - rvm: 2.6 - rvm: 2.7 - rvm: 2.6 env: COVERAGE=BriefSummary,Coveralls - rvm: 2.7 - rvm: ruby-head - rvm: jruby-head env: JRUBY_OPTS="--debug -X+O" - rvm: truffleruby allow_failures: - rvm: ruby-head - rvm: jruby-head - rvm: truffleruby ruby-rspec-memory-1.0.2/Gemfile000066400000000000000000000001411363014607400164060ustar00rootroot00000000000000source 'https://rubygems.org' # Specify your gem's dependencies in rspec-memory.gemspec gemspec ruby-rspec-memory-1.0.2/README.md000066400000000000000000000051671363014607400164070ustar00rootroot00000000000000# RSpec::Memory Make assertions about memory usage. [![Build Status](https://travis-ci.com/socketry/rspec-memory.svg?branch=master)](https://travis-ci.com/socketry/rspec-memory) ## Installation Add this line to your application's Gemfile: ```ruby gem 'rspec-memory' ``` And then execute: $ bundle Or install it yourself as: $ gem install rspec-memory Finally, add this require statement to the top of `spec/spec_helper.rb` ```ruby require 'rspec/memory' ``` ## Usage Allocating large amounts of objects can lead to memory 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. ```ruby RSpec.describe "memory allocations" do include_context RSpec::Memory it "limits allocation counts" do expect do 6.times{String.new} end.to limit_allocations(String => 10) # 10 strings can be allocated end it "limits allocation counts (hash)" do expect do 6.times{String.new} end.to limit_allocations(String => {count: 10}) # 10 strings can be allocated end it "limits allocation size" do expect do 6.times{String.new("foo")} end.to limit_allocations(String => {size: 1024}) # 1 KB of strings can be allocated end end ``` ## Contributing 1. Fork it 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request ## License Released under the MIT license. Copyright, 2017, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams). Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ruby-rspec-memory-1.0.2/Rakefile000066400000000000000000000001651363014607400165660ustar00rootroot00000000000000require "bundler/gem_tasks" require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:test) task :default => :test ruby-rspec-memory-1.0.2/lib/000077500000000000000000000000001363014607400156655ustar00rootroot00000000000000ruby-rspec-memory-1.0.2/lib/rspec/000077500000000000000000000000001363014607400170015ustar00rootroot00000000000000ruby-rspec-memory-1.0.2/lib/rspec/memory.rb000066400000000000000000000023561363014607400206440ustar00rootroot00000000000000# Copyright, 2017, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require_relative 'memory/matchers/limit_allocations' RSpec.shared_context RSpec::Memory do include RSpec::Memory::Matchers end ruby-rspec-memory-1.0.2/lib/rspec/memory/000077500000000000000000000000001363014607400203115ustar00rootroot00000000000000ruby-rspec-memory-1.0.2/lib/rspec/memory/matchers/000077500000000000000000000000001363014607400221175ustar00rootroot00000000000000ruby-rspec-memory-1.0.2/lib/rspec/memory/matchers/limit_allocations.rb000066400000000000000000000074241363014607400261610ustar00rootroot00000000000000# Copyright, 2018, by Samuel G. D. Williams. # Copyright, 2018, by Janko Marohnić. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require_relative '../trace' require 'rspec/expectations' module RSpec module Memory module Matchers class LimitAllocations include RSpec::Matchers::Composable def initialize(allocations = {}, count: nil, size: nil) @count = count @size = size @allocations = {} @errors = [] allocations.each do |klass, count| self.of(klass, count: count) end end def supports_block_expectations? true end def of(klass, **limits) @allocations[klass] = limits return self end private def check(value, limit) case limit when Range unless limit.include? value yield "expected within #{limit}" end when Integer unless value == limit yield "expected exactly #{limit}" end end end def matches?(given_proc) return true unless trace = Trace.capture(@allocations.keys, &given_proc) if @count or @size # If the spec specifies a total limit, we have a limit which we can enforce which takes all allocations into account: total = trace.total check(total.count, @count) do |expected| @errors << "allocated #{total.count} instances, #{total.size} bytes, #{expected} instances" end if @count check(total.size, @size) do |expected| @errors << "allocated #{total.count} instances, #{total.size} bytes, #{expected} bytes" end if @size else # Otherwise unspecified allocations are considered an error: trace.ignored.each do |klass, allocation| @errors << "allocated #{allocation.count} #{klass} instances, #{allocation.size} bytes, but it was not specified" end end trace.allocated.each do |klass, allocation| next unless acceptable = @allocations[klass] check(allocation.count, acceptable[:count]) do |expected| @errors << "allocated #{allocation.count} #{klass} instances, #{allocation.size} bytes, #{expected} instances" end check(allocation.size, acceptable[:size]) do |expected| @errors << "allocated #{allocation.count} #{klass} instances, #{allocation.size} bytes, #{expected} bytes" end end return @errors.empty? end def failure_message "exceeded allocation limit: #{@errors.join(', ')}" end end if respond_to?(:ruby2_keywords, true) def limit_allocations(count: nil, size: nil, **allocations) LimitAllocations.new(allocations, count: count, size: size) end else def limit_allocations(*arguments) LimitAllocations.new(*arguments) end end end end end ruby-rspec-memory-1.0.2/lib/rspec/memory/trace.rb000066400000000000000000000071061363014607400217400ustar00rootroot00000000000000# Copyright, 2017, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'objspace' module RSpec module Memory Allocation = Struct.new(:count, :size) do SLOT_SIZE = 40 def << object self.count += 1 # We don't want to force specs to take the slot size into account. self.size += ObjectSpace.memsize_of(object) - SLOT_SIZE end def self.default_hash Hash.new{|h,k| h[k] = Allocation.new(0, 0)} end end class Trace def self.supported? # There are issues on truffleruby-1.0.0rc9 return false if RUBY_ENGINE == "truffleruby" ObjectSpace.respond_to?(:trace_object_allocations) end if supported? def self.capture(*args, &block) self.new(*args).tap do |trace| trace.capture(&block) end end else def self.capture(*args, &block) yield return nil end end def initialize(klasses) @klasses = klasses @allocated = Allocation.default_hash @retained = Allocation.default_hash @ignored = Allocation.default_hash @total = Allocation.new(0, 0) end attr :allocated attr :retained attr :ignored attr :total def current_objects(generation) allocations = [] ObjectSpace.each_object do |object| if ObjectSpace.allocation_generation(object) == generation allocations << object end end return allocations end def find_base(object) @klasses.find{|klass| object.is_a? klass} end def capture(&block) GC.start begin GC.disable generation = GC.count ObjectSpace.trace_object_allocations(&block) allocated = current_objects(generation) ensure GC.enable end GC.start retained = current_objects(generation) # All allocated objects, including those freed in the last GC: allocated.each do |object| if klass = find_base(object) @allocated[klass] << object else # If the user specified classes, but we can't pin this allocation to a specific class, we issue a warning. if @klasses.any? warn "Ignoring allocation of #{object.class} at #{ObjectSpace.allocation_sourcefile(object)}:#{ObjectSpace.allocation_sourceline(object)}" end @ignored[object.class] << object end @total << object end # Retained objects are still alive after a final GC: retained.each do |object| if klass = find_base(object) @retained[klass] << object end end end end end end ruby-rspec-memory-1.0.2/lib/rspec/memory/version.rb000066400000000000000000000022461363014607400223270ustar00rootroot00000000000000# Copyright, 2019, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. module RSpec module Memory VERSION = "1.0.2" end end ruby-rspec-memory-1.0.2/rspec-memory.gemspec000066400000000000000000000014061363014607400211070ustar00rootroot00000000000000# coding: utf-8 require_relative 'lib/rspec/memory/version' Gem::Specification.new do |spec| spec.name = "rspec-memory" spec.version = RSpec::Memory::VERSION spec.licenses = ["MIT"] spec.authors = ["Samuel Williams"] spec.email = ["samuel.williams@oriontransfer.co.nz"] spec.summary = "RSpec helpers for checking memory allocations." spec.homepage = "https://github.com/socketry/rspec-memory" spec.files = `git ls-files -z`.split("\x0").reject do |f| f.match(%r{^(test|spec|features)/}) end spec.require_paths = ["lib"] spec.add_dependency "rspec", "~> 3.0" spec.add_development_dependency "covered" spec.add_development_dependency "bundler" spec.add_development_dependency "rake", "~> 10.0" end ruby-rspec-memory-1.0.2/spec/000077500000000000000000000000001363014607400160515ustar00rootroot00000000000000ruby-rspec-memory-1.0.2/spec/rspec/000077500000000000000000000000001363014607400171655ustar00rootroot00000000000000ruby-rspec-memory-1.0.2/spec/rspec/memory_spec.rb000066400000000000000000000060101363014607400220310ustar00rootroot00000000000000# Copyright, 2017, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'rspec/memory' RSpec.describe RSpec::Memory do include_context RSpec::Memory it "should execute code in block" do string = nil expect do string = String.new end.to limit_allocations(String => 1) expect(string).to_not be_nil end context "on supported platform", if: RSpec::Memory::Trace.supported? do it "should not exceed specified count limit" do expect do 2.times{String.new} end.to limit_allocations(String => 2) expect do 2.times{String.new} end.to limit_allocations.of(String, count: 2) end it "should fail if there are untracked allocations" do expect do expect do Array.new end.to limit_allocations end.to raise_error(RSpec::Expectations::ExpectationNotMetError, /it was not specified/) end it "should exceed specified count limit" do expect do expect do 6.times{String.new} end.to limit_allocations(String => 4) end.to raise_error(RSpec::Expectations::ExpectationNotMetError, /expected exactly 4 instances/) end if RSpec::Memory::Trace.supported? it "should be within specified count range" do expect do 2.times{String.new} end.to limit_allocations(String => 1..3) expect do 2.times{String.new} end.to limit_allocations.of(String, count: 1..3) end it "should exceed specified count range" do expect do expect do 6.times{String.new} end.to limit_allocations(String => 1..3) end.to raise_error(RSpec::Expectations::ExpectationNotMetError, /expected within 1..3 instances/) end it "should not exceed specified size limit" do expect do "a" * 100_000 end.to limit_allocations.of(String, size: 100_001) end it "should exceed specified size limit" do expect do expect do "a" * 120_000 end.to limit_allocations(size: 100_000) end.to raise_error(RSpec::Expectations::ExpectationNotMetError, /expected exactly 100000 bytes/) end end end ruby-rspec-memory-1.0.2/spec/spec_helper.rb000066400000000000000000000005221363014607400206660ustar00rootroot00000000000000 require 'covered/rspec' RSpec.configure do |config| # Enable flags like --only-failures and --next-failure config.example_status_persistence_file_path = ".rspec_status" # Disable RSpec exposing methods globally on `Module` and `main` config.disable_monkey_patching! config.expect_with :rspec do |c| c.syntax = :expect end end