async-rspec 1.6.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
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