async-rspec 1.12.1 → 1.15.1

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: f433e1d62ca61d5912dbd87faa8b040bd0ae84f178f53da363c95b2e78ddab42
4
- data.tar.gz: ae690667bbcdf40bab65fdf7555b1e066de9a3e38770e0e5aeec6c3659b34710
3
+ metadata.gz: 23eaebca4b0ebf551b1afecc2236c72652dc562c6f971a9f8559287e38de42f8
4
+ data.tar.gz: 1419caee694521f241c52b14f7301ac7330a84dbc4460838fc55a03b11a65ced
5
5
  SHA512:
6
- metadata.gz: f43505247ce2d331e5683c409c6cae1db98fd8b566471c3963dd65e11975126c100a1167689718efe81999c33b7c386dc3b78c2c0fdc47feeb9798eb49ef8e67
7
- data.tar.gz: aca076025b7cedb20339bb2327caa648eca21915c6e6f7740880fb06131462efeb022cb8463f58305614d15e19325491e8cb58cd1e937c95eec6bea99046550e
6
+ metadata.gz: f6aee00dc62b6040de66ea40e0a00b17609fef58f7a88840b2244b6ab95c65604fa6368f3d4a29988b407f3b86713c6ad1c95d83bf6693f44d22e44c338de248
7
+ data.tar.gz: 5b2f7389804fe51274db01e8e4d4baf5e90d0f8d50ef55cb77311ce41726a24d03e1579d131306d2982bbb1ff11e0a387e55b0f18b439b09a9d42388d52ca615
@@ -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,58 @@
20
20
 
21
21
  require_relative 'leaks'
22
22
 
23
+ require 'kernel/async'
24
+
23
25
  require 'async/reactor'
24
26
  require 'async/debug/selector'
25
27
 
26
28
  module Async
27
29
  module RSpec
28
30
  module Reactor
29
- def run_example(reactor, example, duration)
31
+ def notify_failure(exception = $!)
32
+ ::RSpec::Support.notify_failure(exception)
33
+ end
34
+
35
+ def run_in_reactor(reactor, duration = nil)
30
36
  result = nil
31
37
 
38
+ timer_task = nil
39
+
40
+ if duration
41
+ timer_task = reactor.async do |task|
42
+ # Wait for the timeout, at any point this task might be cancelled if the user code completes:
43
+ task.annotate("timer task duration=#{duration}")
44
+ task.sleep(duration)
45
+
46
+ # The timeout expired, so generate an error:
47
+ buffer = StringIO.new
48
+ reactor.print_hierarchy(buffer)
49
+
50
+ # Raise an error so it is logged:
51
+ raise TimeoutError, "run time exceeded duration #{duration}s:\r\n#{buffer.string}"
52
+ end
53
+ end
54
+
32
55
  reactor.run do |task|
33
56
  task.annotate(self.class)
34
57
 
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")
58
+ spec_task = task.async do |spec_task|
59
+ spec_task.annotate("running example")
60
+
61
+ result = yield
54
62
 
55
- result = example.run
63
+ timer_task&.stop
56
64
 
57
- if result.is_a? Exception
58
- reactor.stop
59
- else
60
- spec_task.children.each(&:wait)
61
- end
62
- end.wait
65
+ raise Async::Stop
66
+ end
63
67
 
64
- timer.stop if timer
65
- end
68
+ begin
69
+ timer_task&.wait
70
+ spec_task.wait
71
+ ensure
72
+ spec_task.stop
73
+ end
74
+ end.wait
66
75
 
67
76
  return result
68
77
  end
@@ -79,7 +88,9 @@ module Async
79
88
  duration = example.metadata.fetch(:timeout, 10)
80
89
 
81
90
  begin
82
- run_example(reactor, example, duration)
91
+ run_in_reactor(reactor, duration) do
92
+ example.run
93
+ end
83
94
  ensure
84
95
  reactor.close
85
96
  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.1"
23
+ VERSION = "1.15.1"
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.1
4
+ version: 1.15.1
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-03-03 00:00:00.000000000 Z
11
+ date: 2021-02-13 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: bundler
43
71
  requirement: !ruby/object:Gem::Requirement
44
72
  requirements:
45
73
  - - ">="
@@ -53,19 +81,19 @@ dependencies:
53
81
  - !ruby/object:Gem::Version
54
82
  version: '0'
55
83
  - !ruby/object:Gem::Dependency
56
- name: bundler
84
+ name: covered
57
85
  requirement: !ruby/object:Gem::Requirement
58
86
  requirements:
59
- - - "~>"
87
+ - - ">="
60
88
  - !ruby/object:Gem::Version
61
- version: '1.13'
89
+ version: '0'
62
90
  type: :development
63
91
  prerelease: false
64
92
  version_requirements: !ruby/object:Gem::Requirement
65
93
  requirements:
66
- - - "~>"
94
+ - - ">="
67
95
  - !ruby/object:Gem::Version
68
- version: '1.13'
96
+ version: '0'
69
97
  - !ruby/object:Gem::Dependency
70
98
  name: rake
71
99
  requirement: !ruby/object:Gem::Requirement
@@ -80,35 +108,25 @@ dependencies:
80
108
  - - "~>"
81
109
  - !ruby/object:Gem::Version
82
110
  version: '10.0'
83
- description:
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.2
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", "~> 1.13"
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