async-rspec 1.6.0 → 1.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d82743af426d13dd6463e49155abd2d3a1f4e5111ea1f8dc5607b78c1fd77545
4
- data.tar.gz: ccf15bf3cd995e71e05866e3935f845ef4f42999acda0cf3172ece2eefc1269e
3
+ metadata.gz: b82909dd774fe15191c43b09b44d2276f92538f7602230917d92a90ecc3c9e75
4
+ data.tar.gz: d4e2b8722931ece8de4fd5580b69977aacf07e482df5640d91a66231c0715271
5
5
  SHA512:
6
- metadata.gz: a6e607fd2d6bd68f5414c9ffa87ea306c17bf1ba1c599320855eadfa12998e9f857f066b7781d87c12c6b35c120fc5ed1cffd86647c6f8a24d943712bb5c549a
7
- data.tar.gz: bb604e41f0da0e384ad361b27770aa48c0545ded1290c3d711559ad93c5ed66cb655f3f7c35cc56ab81531d602df2cbe04bfbc7e2ed5aed6b170b7c21677dce9
6
+ metadata.gz: 6699ca96c6db25094e0705cc4e17e8ac4e28a6127f9e57192269a10b1361cda80ebfe5963de1850493f3ef2ba50122c391bf957fcdcba838bea857404fbcee45
7
+ data.tar.gz: 5246399920a6b4dcd41a3070b227e8d93eae3dff252ae2e15651ee9fe4b97d5254d7c110fda490bf2c5475d6f221b43c9e777ff64eede3d767034bfe1169abe4
data/README.md CHANGED
@@ -43,6 +43,34 @@ end
43
43
 
44
44
  In some cases, the Ruby garbage collector will close IOs. In the above case, it's possible to just writing `IO.pipe` will not leak, as Ruby will garbage collect the resulting IOs immediately. It's still incorrect to not correctly close IOs, so don't depend on this behaviour.
45
45
 
46
+ ### Allocations
47
+
48
+ Allocating large amounts of objects can lead to memery problems. `Async::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.
49
+
50
+ ```ruby
51
+ RSpec.describe "memory allocations" do
52
+ include_context Async::RSpec::Memory
53
+
54
+ it "limits allocation counts" do
55
+ expect do
56
+ 6.times{String.new}
57
+ end.to limit_allocations(String => 10) # 10 strings can be allocated
58
+ end
59
+
60
+ it "limits allocation counts (hash)" do
61
+ expect do
62
+ 6.times{String.new}
63
+ end.to limit_allocations(String => { count: 10 }) # 10 strings can be allocated
64
+ end
65
+
66
+ it "limits allocation size" do
67
+ expect do
68
+ 6.times{String.new("foo")}
69
+ end.to limit_allocations(String => { size: 1024 }) # 1 KB of strings can be allocated
70
+ end
71
+ end
72
+ ```
73
+
46
74
  ### Reactor
47
75
 
48
76
  Many specs need to run within a reactor. A shared context is provided which includes all the relevant bits, including the above leaks checks.
@@ -18,128 +18,10 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- require 'rspec/expectations'
22
- require 'objspace'
21
+ require_relative 'memory/limit_allocations'
23
22
 
24
23
  module Async
25
24
  module RSpec
26
- module Memory
27
- Allocation = Struct.new(:count, :size) do
28
- def << object
29
- self.count += 1
30
- self.size += ObjectSpace.memsize_of(object)
31
- end
32
- end
33
-
34
- class Trace
35
- def self.supported?
36
- ObjectSpace.respond_to? :trace_object_allocations
37
- end
38
-
39
- if supported?
40
- def self.capture(&block)
41
- self.new.tap do |trace|
42
- trace.capture(&block)
43
- end
44
- end
45
- else
46
- def self.capture(&block)
47
- yield
48
-
49
- return nil
50
- end
51
- end
52
-
53
- def initialize
54
- @allocated = Hash.new{|h,k| h[k] = Allocation.new(0, 0)}
55
- @retained = Hash.new{|h,k| h[k] = Allocation.new(0, 0)}
56
- end
57
-
58
- attr :allocated
59
- attr :retained
60
-
61
- def current_objects(generation)
62
- allocations = []
63
-
64
- ObjectSpace.each_object do |object|
65
- if ObjectSpace.allocation_generation(object) == generation
66
- allocations << object
67
- end
68
- end
69
-
70
- return allocations
71
- end
72
-
73
- def capture(&block)
74
- allocated = nil
75
-
76
- begin
77
- GC.disable
78
-
79
- generation = GC.count
80
- ObjectSpace.trace_object_allocations(&block)
81
-
82
- allocated = current_objects(generation)
83
- ensure
84
- GC.enable
85
- end
86
-
87
- GC.start
88
- retained = current_objects(generation)
89
-
90
- allocated.each do |object|
91
- @allocated[object.class] << object
92
- end
93
-
94
- retained.each do |object|
95
- @retained[object.class] << object
96
- end
97
- end
98
- end
99
-
100
- class LimitAllocations
101
- include ::RSpec::Matchers::Composable
102
-
103
- def initialize(allocations)
104
- @allocations = allocations
105
- @errors = []
106
- end
107
-
108
- def supports_block_expectations?
109
- true
110
- end
111
-
112
- def matches?(given_proc)
113
- return true unless trace = Trace.capture(&given_proc)
114
-
115
- @allocations.each do |klass, acceptable|
116
- next unless allocation = allocation = trace.allocated[klass]
117
-
118
- case acceptable
119
- when Range
120
- unless acceptable.include? allocation.count
121
- @errors << "allocated #{allocation.count} instances (#{allocation.size} bytes) of #{klass}, expected within #{acceptable}"
122
- end
123
- when Integer
124
- if allocation.count > acceptable
125
- @errors << "allocated #{allocation.count} instances (#{allocation.size} bytes) of #{klass}, expected at most #{acceptable}"
126
- end
127
- end
128
- end
129
-
130
- return @errors.empty?
131
- end
132
-
133
- def failure_message
134
- "exceeded allocation limit: #{@errors.join(', ')}"
135
- end
136
- end
137
-
138
- def limit_allocations(allocations)
139
- LimitAllocations.new(allocations)
140
- end
141
- end
142
-
143
25
  RSpec.shared_context Memory do
144
26
  include Memory
145
27
  end
@@ -0,0 +1,107 @@
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 Async
27
+ module RSpec
28
+ module Memory
29
+ # expect{...}.to allocate < 10.megabytes
30
+ #
31
+ class LimitAllocations
32
+ include ::RSpec::Matchers::Composable
33
+
34
+ def initialize(allocations = {}, count: nil, size: nil)
35
+ @count = count
36
+ @size = size
37
+
38
+ @allocations = {}
39
+ @errors = []
40
+
41
+ allocations.each do |klass, count|
42
+ self.of(klass, count: count)
43
+ end
44
+ end
45
+
46
+ def supports_block_expectations?
47
+ true
48
+ end
49
+
50
+ def of(klass, **limits)
51
+ @allocations[klass] = limits
52
+
53
+ return self
54
+ end
55
+
56
+ private def check(value, limit)
57
+ case limit
58
+ when Range
59
+ unless limit.include? value
60
+ yield "expected within #{limit}"
61
+ end
62
+ when Integer
63
+ unless value < limit
64
+ yield "expected at most #{limit}"
65
+ end
66
+ end
67
+ end
68
+
69
+ def matches?(given_proc)
70
+ return true unless trace = Trace.capture(&given_proc)
71
+
72
+ if total = trace.total
73
+ check(total.count, @count) do |expected|
74
+ @errors << "allocated #{total.count} instances, #{total.size} bytes, #{expected} instances"
75
+ end if @count
76
+
77
+ check(total.size, @size) do |expected|
78
+ @errors << "allocated #{total.count} instances, #{total.size} bytes, #{expected} bytes"
79
+ end if @size
80
+ end
81
+
82
+ @allocations.each do |klass, acceptable|
83
+ next unless allocation = trace.allocated[klass]
84
+
85
+ check(allocation.count, acceptable[:count]) do |expected|
86
+ @errors << "allocated #{allocation.count} #{klass} instances, #{allocation.size} bytes, #{expected} instances"
87
+ end
88
+
89
+ check(allocation.size, acceptable[:size]) do |expected|
90
+ @errors << "allocated #{allocation.count} #{klass} instances, #{allocation.size} bytes, #{expected} instances"
91
+ end
92
+ end
93
+
94
+ return @errors.empty?
95
+ end
96
+
97
+ def failure_message
98
+ "exceeded allocation limit: #{@errors.join(', ')}"
99
+ end
100
+ end
101
+
102
+ def limit_allocations(allocations)
103
+ LimitAllocations.new(allocations)
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,107 @@
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 Async
24
+ module RSpec
25
+ module Memory
26
+ Allocation = Struct.new(:count, :size) do
27
+ def << object
28
+ self.count += 1
29
+ self.size += ObjectSpace.memsize_of(object)
30
+ end
31
+ end
32
+
33
+ class Trace
34
+ def self.supported?
35
+ ObjectSpace.respond_to? :trace_object_allocations
36
+ end
37
+
38
+ if supported?
39
+ def self.capture(&block)
40
+ self.new.tap do |trace|
41
+ trace.capture(&block)
42
+ end
43
+ end
44
+ else
45
+ def self.capture(&block)
46
+ yield
47
+
48
+ return nil
49
+ end
50
+ end
51
+
52
+ def initialize
53
+ @allocated = Hash.new{|h,k| h[k] = Allocation.new(0, 0)}
54
+ @retained = Hash.new{|h,k| h[k] = Allocation.new(0, 0)}
55
+
56
+ @total = Allocation.new(0, 0)
57
+ end
58
+
59
+ attr :allocated
60
+ attr :retained
61
+
62
+ attr :total
63
+
64
+ def current_objects(generation)
65
+ allocations = []
66
+
67
+ ObjectSpace.each_object do |object|
68
+ if ObjectSpace.allocation_generation(object) == generation
69
+ allocations << object
70
+ end
71
+ end
72
+
73
+ return allocations
74
+ end
75
+
76
+ def capture(&block)
77
+ allocated = nil
78
+
79
+ begin
80
+ GC.disable
81
+
82
+ generation = GC.count
83
+ ObjectSpace.trace_object_allocations(&block)
84
+
85
+ allocated = current_objects(generation)
86
+ ensure
87
+ GC.enable
88
+ end
89
+
90
+ GC.start
91
+ retained = current_objects(generation)
92
+
93
+ # All allocated objects, including those freed in the last GC:
94
+ allocated.each do |object|
95
+ @allocated[object.class] << object
96
+ @total << object
97
+ end
98
+
99
+ # Retained objects are still alive after a final GC:
100
+ retained.each do |object|
101
+ @retained[object.class] << object
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -20,6 +20,6 @@
20
20
 
21
21
  module Async
22
22
  module RSpec
23
- VERSION = "1.6.0"
23
+ VERSION = "1.7.0"
24
24
  end
25
25
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-rspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.0
4
+ version: 1.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-06-09 00:00:00.000000000 Z
11
+ date: 2018-07-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -83,6 +83,8 @@ files:
83
83
  - lib/async/rspec.rb
84
84
  - lib/async/rspec/leaks.rb
85
85
  - lib/async/rspec/memory.rb
86
+ - lib/async/rspec/memory/limit_allocations.rb
87
+ - lib/async/rspec/memory/trace.rb
86
88
  - lib/async/rspec/profile.rb
87
89
  - lib/async/rspec/reactor.rb
88
90
  - lib/async/rspec/ssl.rb