async-rspec 1.12.2 → 1.16.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: 8eb3614b4300ca02cbb6299fbeb1606f70a6d090536cde9b5e66ecb53b52833d
4
- data.tar.gz: 46f5a934a79a1cd5d500a1f5377439e248960ba63f2c79d52f72ffc7117d3dec
3
+ metadata.gz: 99cc9f67cfd8ad26555a5805dc8aa76b58876272ea0aecd18c07434ee8304c18
4
+ data.tar.gz: de573e462396eb1ccd28a45f7bd345bd2ecd0e8e6ea814ec300775b51d16280d
5
5
  SHA512:
6
- metadata.gz: 7b18b90865cc67ab259e53905e87fc1c3038bcdb28fd4ed4db46f0adead7e082d07a9d8dfcc7e8774c828aa4d245331ae438b669293186d771345f2ac24cb682
7
- data.tar.gz: 1b74916a02bb261d73978d7b06b9149d8b8d247a45b050ac026a17d3ef7137b168a2ab889def16c071c11ea56e3eaa9f50ff6afb592baab52d055c07565f7229
6
+ metadata.gz: ef693569b536cbba80c35d7805de22cd2cda1046f125cb3516c432b6548587042269cc802989fb79d94aa032c96eb45c589fd275f1c891462fb71337affdcdf5
7
+ data.tar.gz: fc6a89c0a9cecc214e140fb9a3b0887f6304f8612aeb1b6e771babda377d993fa56e6f2ef223fd02800123f24fd64005ceea500f0420df1fc29d7da039152d2a
@@ -18,31 +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/files'
22
+
21
23
  module Async
22
24
  module RSpec
23
- module Leaks
24
- def current_ios(gc: GC.start)
25
- all_ios = ObjectSpace.each_object(::IO).to_a.sort_by(&:object_id)
26
-
27
- # We are not interested in ios that have been closed already:
28
- return all_ios.reject{|io| io.closed?}
29
- end
30
- end
31
-
32
- ::RSpec.shared_context Leaks do
33
- include Leaks
34
-
35
- let(:before_ios) {current_ios}
36
- let(:after_ios) {current_ios}
37
-
38
- # We use around(:each) because it's the highest priority.
39
- around(:each) do |example|
40
- before_ios
41
-
42
- example.run.tap do
43
- expect(after_ios).to be == before_ios
44
- end
45
- end
46
- end
25
+ Leaks = ::RSpec::Files::Leaks
47
26
  end
48
27
  end
@@ -18,12 +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_relative 'memory/limit_allocations'
21
+ require 'rspec/memory'
22
22
 
23
23
  module Async
24
24
  module RSpec
25
- ::RSpec.shared_context Memory do
26
- include Memory
27
- end
25
+ Memory = ::RSpec::Memory
28
26
  end
29
27
  end
@@ -20,49 +20,56 @@
20
20
 
21
21
  require_relative 'leaks'
22
22
 
23
+ require 'kernel/async'
23
24
  require 'async/reactor'
24
- require 'async/debug/selector'
25
25
 
26
26
  module Async
27
27
  module RSpec
28
28
  module Reactor
29
- def run_example(reactor, example, duration)
29
+ def notify_failure(exception = $!)
30
+ ::RSpec::Support.notify_failure(exception)
31
+ end
32
+
33
+ def run_in_reactor(reactor, duration = nil)
30
34
  result = nil
31
35
 
36
+ timer_task = nil
37
+
38
+ if duration
39
+ timer_task = reactor.async do |task|
40
+ # Wait for the timeout, at any point this task might be cancelled if the user code completes:
41
+ task.annotate("timer task duration=#{duration}")
42
+ task.sleep(duration)
43
+
44
+ # The timeout expired, so generate an error:
45
+ buffer = StringIO.new
46
+ reactor.print_hierarchy(buffer)
47
+
48
+ # Raise an error so it is logged:
49
+ raise TimeoutError, "run time exceeded duration #{duration}s:\r\n#{buffer.string}"
50
+ end
51
+ end
52
+
32
53
  reactor.run do |task|
33
54
  task.annotate(self.class)
34
55
 
35
- reactor = task.reactor
36
- timer = nil
37
-
38
- if duration
39
- timer = reactor.async do |task|
40
- task.annotate("timer task duration=#{duration}")
41
- task.sleep(duration)
42
-
43
- buffer = StringIO.new
44
- reactor.print_hierarchy(buffer)
45
-
46
- reactor.stop
47
-
48
- raise TimeoutError, "run time exceeded duration #{duration}s:\r\n#{buffer.string}"
49
- end
50
- end
51
-
52
- task.async do |spec_task|
53
- spec_task.annotate("example runner")
56
+ spec_task = task.async do |spec_task|
57
+ spec_task.annotate("running example")
54
58
 
55
- result = example.run
59
+ result = yield
56
60
 
57
- if result.is_a? Exception
58
- reactor.stop
59
- else
60
- spec_task.children&.each(&:wait)
61
- end
62
- end.wait
61
+ timer_task&.stop
62
+
63
+ raise Async::Stop
64
+ end
63
65
 
64
- timer.stop if timer
65
- end
66
+ begin
67
+ timer_task&.wait
68
+ spec_task.wait
69
+ ensure
70
+ spec_task.stop
71
+ end
72
+ end.wait
66
73
 
67
74
  return result
68
75
  end
@@ -70,8 +77,7 @@ module Async
70
77
 
71
78
  ::RSpec.shared_context Reactor do
72
79
  include Reactor
73
-
74
- let(:reactor) {Async::Reactor.new(selector: Async::Debug::Selector.new)}
80
+ let(:reactor) {Async::Reactor.new}
75
81
 
76
82
  include_context Async::RSpec::Leaks
77
83
 
@@ -79,7 +85,9 @@ module Async
79
85
  duration = example.metadata.fetch(:timeout, 10)
80
86
 
81
87
  begin
82
- run_example(reactor, example, duration)
88
+ run_in_reactor(reactor, duration) do
89
+ example.run
90
+ end
83
91
  ensure
84
92
  reactor.close
85
93
  end
@@ -41,8 +41,8 @@ module Async
41
41
 
42
42
  ::RSpec.shared_context SSL::CertificateAuthority do
43
43
  # This key size is generally considered insecure, but it's fine for testing.
44
- let(:certificate_authority_key) {OpenSSL::PKey::RSA.new(1024)}
45
- let(:certificate_authority_name) {OpenSSL::X509::Name.parse("O=Test/CN=localhost")}
44
+ let(:certificate_authority_key) {OpenSSL::PKey::RSA.new(2048)}
45
+ let(:certificate_authority_name) {OpenSSL::X509::Name.parse("O=TestCA/CN=localhost")}
46
46
 
47
47
  # The certificate authority is used for signing and validating the certificate which is used for communciation:
48
48
  let(:certificate_authority) do
@@ -83,7 +83,7 @@ module Async
83
83
  include_context SSL::CertificateAuthority
84
84
 
85
85
  # The private key to use on the server side:
86
- let(:key) {OpenSSL::PKey::RSA.new(1024)}
86
+ let(:key) {OpenSSL::PKey::RSA.new(2048)}
87
87
  let(:certificate_name) {OpenSSL::X509::Name.parse("O=Test/CN=localhost")}
88
88
 
89
89
  # The certificate used for actual communication:
@@ -115,7 +115,7 @@ module Async
115
115
 
116
116
  let(:keys) do
117
117
  Hash[
118
- hosts.collect{|name| [name, OpenSSL::PKey::RSA.new(1024)]}
118
+ hosts.collect{|name| [name, OpenSSL::PKey::RSA.new(2048)]}
119
119
  ]
120
120
  end
121
121
 
@@ -177,8 +177,8 @@ module Async
177
177
  include_context SSL::CertificateAuthority
178
178
 
179
179
  # The private key to use on the server side:
180
- let(:key) {OpenSSL::PKey::RSA.new(1024)}
181
- let(:invalid_key) {OpenSSL::PKey::RSA.new(1024)}
180
+ let(:key) {OpenSSL::PKey::RSA.new(2048)}
181
+ let(:invalid_key) {OpenSSL::PKey::RSA.new(2048)}
182
182
  let(:certificate_name) {OpenSSL::X509::Name.parse("O=Test/CN=localhost")}
183
183
 
184
184
  # The certificate used for actual communication:
@@ -20,6 +20,6 @@
20
20
 
21
21
  module Async
22
22
  module RSpec
23
- VERSION = "1.12.2"
23
+ VERSION = "1.16.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.12.2
4
+ version: 1.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-04-28 00:00:00.000000000 Z
11
+ date: 2021-04-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -24,22 +24,50 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec-files
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec-memory
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
27
55
  - !ruby/object:Gem::Dependency
28
56
  name: async
29
57
  requirement: !ruby/object:Gem::Requirement
30
58
  requirements:
31
59
  - - "~>"
32
60
  - !ruby/object:Gem::Version
33
- version: '1.8'
61
+ version: '1.24'
34
62
  type: :development
35
63
  prerelease: false
36
64
  version_requirements: !ruby/object:Gem::Requirement
37
65
  requirements:
38
66
  - - "~>"
39
67
  - !ruby/object:Gem::Version
40
- version: '1.8'
68
+ version: '1.24'
41
69
  - !ruby/object:Gem::Dependency
42
- name: covered
70
+ name: async-io
43
71
  requirement: !ruby/object:Gem::Requirement
44
72
  requirements:
45
73
  - - ">="
@@ -67,48 +95,38 @@ dependencies:
67
95
  - !ruby/object:Gem::Version
68
96
  version: '0'
69
97
  - !ruby/object:Gem::Dependency
70
- name: rake
98
+ name: covered
71
99
  requirement: !ruby/object:Gem::Requirement
72
100
  requirements:
73
- - - "~>"
101
+ - - ">="
74
102
  - !ruby/object:Gem::Version
75
- version: '10.0'
103
+ version: '0'
76
104
  type: :development
77
105
  prerelease: false
78
106
  version_requirements: !ruby/object:Gem::Requirement
79
107
  requirements:
80
- - - "~>"
108
+ - - ">="
81
109
  - !ruby/object:Gem::Version
82
- version: '10.0'
83
- description:
110
+ version: '0'
111
+ description:
84
112
  email:
85
- - samuel.williams@oriontransfer.co.nz
86
113
  executables: []
87
114
  extensions: []
88
115
  extra_rdoc_files: []
89
116
  files:
90
- - ".editorconfig"
91
- - ".gitignore"
92
- - ".rspec"
93
- - ".travis.yml"
94
- - Gemfile
95
- - README.md
96
- - Rakefile
97
- - async-rspec.gemspec
98
117
  - lib/async/rspec.rb
99
118
  - lib/async/rspec/buffer.rb
100
119
  - lib/async/rspec/leaks.rb
101
120
  - lib/async/rspec/memory.rb
102
- - lib/async/rspec/memory/limit_allocations.rb
103
- - lib/async/rspec/memory/trace.rb
104
121
  - lib/async/rspec/profile.rb
105
122
  - lib/async/rspec/reactor.rb
106
123
  - lib/async/rspec/ssl.rb
107
124
  - lib/async/rspec/version.rb
108
125
  homepage: https://github.com/socketry/async-rspec
109
- licenses: []
126
+ licenses:
127
+ - MIT
110
128
  metadata: {}
111
- post_install_message:
129
+ post_install_message:
112
130
  rdoc_options: []
113
131
  require_paths:
114
132
  - lib
@@ -123,8 +141,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
123
141
  - !ruby/object:Gem::Version
124
142
  version: '0'
125
143
  requirements: []
126
- rubygems_version: 3.0.3
127
- signing_key:
144
+ rubygems_version: 3.2.3
145
+ signing_key:
128
146
  specification_version: 4
129
147
  summary: Helpers for writing specs against the async gem.
130
148
  test_files: []
data/.editorconfig DELETED
@@ -1,6 +0,0 @@
1
- root = true
2
-
3
- [*]
4
- indent_style = tab
5
- indent_size = 2
6
-
data/.gitignore DELETED
@@ -1,12 +0,0 @@
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
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --warnings
3
- --require spec_helper
data/.travis.yml DELETED
@@ -1,19 +0,0 @@
1
- language: ruby
2
- cache: bundler
3
-
4
- matrix:
5
- include:
6
- - rvm: 2.3
7
- - rvm: 2.4
8
- - rvm: 2.5
9
- - rvm: 2.6
10
- - rvm: 2.6
11
- env: COVERAGE=BriefSummary,Coveralls
12
- - rvm: ruby-head
13
- - rvm: jruby-head
14
- env: JRUBY_OPTS="--debug -X+O"
15
- - rvm: truffleruby
16
- allow_failures:
17
- - rvm: ruby-head
18
- - rvm: jruby-head
19
- - rvm: truffleruby
data/Gemfile DELETED
@@ -1,8 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in async-rspec.gemspec
4
- gemspec
5
-
6
- group :test do
7
- gem "ruby-prof", platform: :mri
8
- end
data/README.md DELETED
@@ -1,144 +0,0 @@
1
- # Async::RSpec
2
-
3
- Provides useful `RSpec.shared_context`s for testing code that builds on top of [async].
4
-
5
- [async]: https://github.com/socketry/async
6
-
7
- [![Build Status](https://secure.travis-ci.org/socketry/async-rspec.svg)](http://travis-ci.org/socketry/async-rspec)
8
- [![Code Climate](https://codeclimate.com/github/socketry/async-rspec.svg)](https://codeclimate.com/github/socketry/async-rspec)
9
- [![Coverage Status](https://coveralls.io/repos/socketry/async-rspec/badge.svg)](https://coveralls.io/r/socketry/async-rspec)
10
-
11
- ## Installation
12
-
13
- Add this line to your application's Gemfile:
14
-
15
- ```ruby
16
- gem 'async-rspec'
17
- ```
18
-
19
- And then execute:
20
-
21
- $ bundle
22
-
23
- Or install it yourself as:
24
-
25
- $ gem install async-rspec
26
-
27
- Finally, add this require statement to the top of `spec/spec_helper.rb`
28
-
29
- ```ruby
30
- require 'async/rspec'
31
- ```
32
-
33
- ## Usage
34
-
35
- ### Leaks
36
-
37
- Leaking sockets and other kinds of IOs are a problem for long running services. `Async::RSpec::Leaks` tracks all open sockets both before and after the spec. If any are left open, a `RuntimeError` is raised and the spec fails.
38
-
39
- ```ruby
40
- RSpec.describe "leaky ios" do
41
- include_context Async::RSpec::Leaks
42
-
43
- # The following fails:
44
- it "leaks io" do
45
- @input, @output = IO.pipe
46
- end
47
- end
48
- ```
49
-
50
- In some cases, the Ruby garbage collector will close IOs. In the above case, it's possible that just writing `IO.pipe` will not leak as Ruby will garbage collect the resulting IOs immediately. It's still incorrect to not close IOs, so don't depend on this behaviour.
51
-
52
- ### Allocations
53
-
54
- 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.
55
-
56
- ```ruby
57
- RSpec.describe "memory allocations" do
58
- include_context Async::RSpec::Memory
59
-
60
- it "limits allocation counts" do
61
- expect do
62
- 6.times{String.new}
63
- end.to limit_allocations(String => 10) # 10 strings can be allocated
64
- end
65
-
66
- it "limits allocation counts (hash)" do
67
- expect do
68
- 6.times{String.new}
69
- end.to limit_allocations(String => {count: 10}) # 10 strings can be allocated
70
- end
71
-
72
- it "limits allocation size" do
73
- expect do
74
- 6.times{String.new("foo")}
75
- end.to limit_allocations(String => {size: 1024}) # 1 KB of strings can be allocated
76
- end
77
- end
78
- ```
79
-
80
- ### Reactor
81
-
82
- Many specs need to run within a reactor. A shared context is provided which includes all the relevant bits, including the above leaks checks. If your spec fails to run in less than 10 seconds, an `Async::TimeoutError` raises to prevent your test suite from hanging.
83
-
84
- ```ruby
85
- require 'async/io'
86
-
87
- RSpec.describe Async::IO do
88
- include_context Async::RSpec::Reactor
89
-
90
- let(:pipe) {IO.pipe}
91
- let(:input) {Async::IO::Generic.new(pipe.first)}
92
- let(:output) {Async::IO::Generic.new(pipe.last)}
93
-
94
- it "should send and receive data within the same reactor" do
95
- message = nil
96
-
97
- output_task = reactor.async do
98
- message = input.read(1024)
99
- end
100
-
101
- reactor.async do
102
- output.write("Hello World")
103
- end
104
-
105
- output_task.wait
106
- expect(message).to be == "Hello World"
107
-
108
- input.close
109
- output.close
110
- end
111
- end
112
- ```
113
-
114
- ## Contributing
115
-
116
- 1. Fork it
117
- 2. Create your feature branch (`git checkout -b my-new-feature`)
118
- 3. Commit your changes (`git commit -am 'Add some feature'`)
119
- 4. Push to the branch (`git push origin my-new-feature`)
120
- 5. Create new Pull Request
121
-
122
- ## License
123
-
124
- Released under the MIT license.
125
-
126
- Copyright, 2017, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
127
-
128
- Permission is hereby granted, free of charge, to any person obtaining a copy
129
- of this software and associated documentation files (the "Software"), to deal
130
- in the Software without restriction, including without limitation the rights
131
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
132
- copies of the Software, and to permit persons to whom the Software is
133
- furnished to do so, subject to the following conditions:
134
-
135
- The above copyright notice and this permission notice shall be included in
136
- all copies or substantial portions of the Software.
137
-
138
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
139
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
140
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
141
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
142
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
143
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
144
- THE SOFTWARE.
data/Rakefile DELETED
@@ -1,6 +0,0 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
3
-
4
- RSpec::Core::RakeTask.new(:test)
5
-
6
- task :default => :test
data/async-rspec.gemspec DELETED
@@ -1,27 +0,0 @@
1
- # coding: utf-8
2
- require_relative 'lib/async/rspec/version'
3
-
4
- Gem::Specification.new do |spec|
5
- spec.name = "async-rspec"
6
- spec.version = Async::RSpec::VERSION
7
- spec.authors = ["Samuel Williams"]
8
- spec.email = ["samuel.williams@oriontransfer.co.nz"]
9
-
10
- spec.summary = "Helpers for writing specs against the async gem."
11
- spec.homepage = "https://github.com/socketry/async-rspec"
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
- # Since we test the shared contexts, we need some bits of async:
22
- spec.add_development_dependency "async", "~> 1.8"
23
-
24
- spec.add_development_dependency "covered"
25
- spec.add_development_dependency "bundler"
26
- spec.add_development_dependency "rake", "~> 10.0"
27
- end
@@ -1,115 +0,0 @@
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 exactly #{limit}"
65
- end
66
- end
67
- end
68
-
69
- def matches?(given_proc)
70
- return true unless trace = Trace.capture(@allocations.keys, &given_proc)
71
-
72
- if @count or @size
73
- # If the spec specifies a total limit, we have a limit which we can enforce which takes all allocations into account:
74
- total = trace.total
75
-
76
- check(total.count, @count) do |expected|
77
- @errors << "allocated #{total.count} instances, #{total.size} bytes, #{expected} instances"
78
- end if @count
79
-
80
- check(total.size, @size) do |expected|
81
- @errors << "allocated #{total.count} instances, #{total.size} bytes, #{expected} bytes"
82
- end if @size
83
- else
84
- # Otherwise unspecified allocations are considered an error:
85
- trace.ignored.each do |klass, allocation|
86
- @errors << "allocated #{allocation.count} #{klass} instances, #{allocation.size} bytes, but it was not specified"
87
- end
88
- end
89
-
90
- trace.allocated.each do |klass, allocation|
91
- next unless acceptable = @allocations[klass]
92
-
93
- check(allocation.count, acceptable[:count]) do |expected|
94
- @errors << "allocated #{allocation.count} #{klass} instances, #{allocation.size} bytes, #{expected} instances"
95
- end
96
-
97
- check(allocation.size, acceptable[:size]) do |expected|
98
- @errors << "allocated #{allocation.count} #{klass} instances, #{allocation.size} bytes, #{expected} bytes"
99
- end
100
- end
101
-
102
- return @errors.empty?
103
- end
104
-
105
- def failure_message
106
- "exceeded allocation limit: #{@errors.join(', ')}"
107
- end
108
- end
109
-
110
- def limit_allocations(*args)
111
- LimitAllocations.new(*args)
112
- end
113
- end
114
- end
115
- end
@@ -1,140 +0,0 @@
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
- SLOT_SIZE = 40
28
-
29
- def << object
30
- self.count += 1
31
-
32
- # We don't want to force specs to take the slot size into account.
33
- self.size += ObjectSpace.memsize_of(object) - SLOT_SIZE
34
- end
35
-
36
- def self.default_hash
37
- Hash.new{|h,k| h[k] = Allocation.new(0, 0)}
38
- end
39
- end
40
-
41
- class Trace
42
- def self.supported?
43
- # There are issues on truffleruby-1.0.0rc9
44
- return false if RUBY_ENGINE == "truffleruby"
45
-
46
- ObjectSpace.respond_to?(:trace_object_allocations)
47
- end
48
-
49
- if supported?
50
- def self.capture(*args, &block)
51
- self.new(*args).tap do |trace|
52
- trace.capture(&block)
53
- end
54
- end
55
- else
56
- def self.capture(*args, &block)
57
- yield
58
-
59
- return nil
60
- end
61
- end
62
-
63
- def initialize(klasses)
64
- @klasses = klasses
65
-
66
- @allocated = Allocation.default_hash
67
- @retained = Allocation.default_hash
68
-
69
- @ignored = Allocation.default_hash
70
-
71
- @total = Allocation.new(0, 0)
72
- end
73
-
74
- attr :allocated
75
- attr :retained
76
-
77
- attr :ignored
78
-
79
- attr :total
80
-
81
- def current_objects(generation)
82
- allocations = []
83
-
84
- ObjectSpace.each_object do |object|
85
- if ObjectSpace.allocation_generation(object) == generation
86
- allocations << object
87
- end
88
- end
89
-
90
- return allocations
91
- end
92
-
93
- def find_base(object)
94
- @klasses.find{|klass| object.is_a? klass}
95
- end
96
-
97
- def capture(&block)
98
- GC.start
99
-
100
- begin
101
- GC.disable
102
-
103
- generation = GC.count
104
- ObjectSpace.trace_object_allocations(&block)
105
-
106
- allocated = current_objects(generation)
107
- ensure
108
- GC.enable
109
- end
110
-
111
- GC.start
112
- retained = current_objects(generation)
113
-
114
- # All allocated objects, including those freed in the last GC:
115
- allocated.each do |object|
116
- if klass = find_base(object)
117
- @allocated[klass] << object
118
- else
119
- # If the user specified classes, but we can't pin this allocation to a specific class, we issue a warning.
120
- if @klasses.any?
121
- warn "Ignoring allocation of #{object.class} at #{ObjectSpace.allocation_sourcefile(object)}:#{ObjectSpace.allocation_sourceline(object)}"
122
- end
123
-
124
- @ignored[object.class] << object
125
- end
126
-
127
- @total << object
128
- end
129
-
130
- # Retained objects are still alive after a final GC:
131
- retained.each do |object|
132
- if klass = find_base(object)
133
- @retained[klass] << object
134
- end
135
- end
136
- end
137
- end
138
- end
139
- end
140
- end