rack_request_blocker 1.0.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 +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +71 -0
- data/Rakefile +1 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/rack_request_blocker.rb +84 -0
- data/lib/rack_request_blocker/version.rb +3 -0
- data/rack_request_blocker.gemspec +26 -0
- metadata +99 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fd8daaa9167b20955f101d46c2cd3704662bbd0d
|
4
|
+
data.tar.gz: 76ef0f2690f361bd96a8eb120203290b0f16b041
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fcb945a9a11eafa3890f62e97a51b1c9b40e51f0ad4e10ab368638ad5102af9def9255fc5710b07568d36597cfe1f7a67028ab0f773e27c93827ff6788ce4f4d
|
7
|
+
data.tar.gz: e1108cb8c063a3a1f5c6857f284f1fc5c6a7cb0d7845aa7d083a0f8bedea15e3df60a2868133075df9cfd71606a0da068a12840afbce246c25a6a4dee6563d4f
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Jonathan Rochkind
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# RackRequestBlocker
|
2
|
+
|
3
|
+
Handle race conditions in Rails JS feature tests caused by outstanding overlapping actions in your app, with rack middleware to let you wait for pending feature test actions to finish.
|
4
|
+
|
5
|
+
For background on why this might be a problem you want to handle, see:
|
6
|
+
* http://blog.salsify.com/engineering/tearing-capybara-ajax-tests
|
7
|
+
* https://bibwild.wordpress.com/2016/02/18/struggling-towards-reliable-capybara-javascript-testing/
|
8
|
+
|
9
|
+
This gem is based on a concept and code [originally by Joel Turkel](http://blog.salsify.com/engineering/tearing-capybara-ajax-tests), but updated to use [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby) as a dependency, with signal/wait logic instead of polling.
|
10
|
+
|
11
|
+
**WARNING**: This code is somewhat experimental, concurrency is hard, it may have bugs. It currently
|
12
|
+
has no automated tests (testing concurrency is painful). But it's working for me, and you
|
13
|
+
only use it in test environment anyway, so if you're suffering from horrible race
|
14
|
+
conditions in test, it's worth a shot to see if it helps, and entails little risk.
|
15
|
+
It's also only a few dozen lines of code in one class, please do look at the source.
|
16
|
+
|
17
|
+
## Installation/Usage
|
18
|
+
|
19
|
+
1. Add this line to your application's Gemfile:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
gem 'rack_request_blocker'
|
23
|
+
```
|
24
|
+
|
25
|
+
And then execute:
|
26
|
+
|
27
|
+
$ bundle
|
28
|
+
|
29
|
+
Or install it yourself as:
|
30
|
+
|
31
|
+
$ gem install rack_request_blocker
|
32
|
+
|
33
|
+
2. Add this to your `./config/environments/test.rb`, to add the custom middleware in test env:
|
34
|
+
|
35
|
+
~~~ruby
|
36
|
+
require 'rack_request_blocker'
|
37
|
+
config.middleware.insert_before 0, RackRequestBlocker
|
38
|
+
~~~
|
39
|
+
|
40
|
+
3. Add this to your `spec_helper.rb`/`rails_helper.rb`
|
41
|
+
|
42
|
+
Add _before_ your `DatabaseCleaner.clean` command, probably in a `before(:each)`:
|
43
|
+
|
44
|
+
~~~ruby
|
45
|
+
if example.metadata[:js] || example.metadata[:driver] != :rack_test
|
46
|
+
RackRequestBlocker.wait_for_no_active_requests(for_example: example)
|
47
|
+
end
|
48
|
+
~~~
|
49
|
+
|
50
|
+
This says for each JS test, block incoming requests to the embedded dummy Rails app,
|
51
|
+
and then wait until any in-progress request actions are complete, before proceeding
|
52
|
+
(to your `DatabaseCleaner.clean`, and then on to the next test. ) It relies on the
|
53
|
+
custom middleware to be able to do so.
|
54
|
+
|
55
|
+
## Will this take care of all my unreliable test issues?
|
56
|
+
|
57
|
+
I found I **also** needed to stop using transactional testing *entirely*.
|
58
|
+
No `:transaction` strategy for DatabaseCleaner *at all*, in *addition* to this
|
59
|
+
middle-ware, to get reliable tests.
|
60
|
+
|
61
|
+
I can't explain why: Using transactional strategy for *non-JS tests*, as
|
62
|
+
DatabaseCleaner recommends *ought* to work. But it didn't, and I even got
|
63
|
+
some segfaults(!) with only the middleware but still transactional strategy
|
64
|
+
for non-JS tests. Could be a bug in MRI or the pg or poltegeist gems (segfault, really?),
|
65
|
+
or capybara, databasecleaner, or the test_after_commit gem we were using
|
66
|
+
with transactional testing strategy. I dunno, debugging this stuff
|
67
|
+
is *hard*... but this seems to have finally worked.
|
68
|
+
|
69
|
+
If you are still having problems, I recommend consulting
|
70
|
+
[my essay on an overview of the issues](https://bibwild.wordpress.com/2016/02/18/struggling-towards-reliable-capybara-javascript-testing/) for background and more ideas.
|
71
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "rack_request_blocker"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
require "rack_request_blocker/version"
|
2
|
+
require 'concurrent'
|
3
|
+
|
4
|
+
# Rack middleware that keeps track of the number of active requests and can block new requests.
|
5
|
+
# http://blog.salsify.com/engineering/tearing-capybara-ajax-tests
|
6
|
+
class RackRequestBlocker
|
7
|
+
@@num_active_requests = Concurrent::AtomicFixnum.new(0)
|
8
|
+
@@allowing_requests = Concurrent::Event.new.tap { |e| e.set }
|
9
|
+
|
10
|
+
# Returns the number of requests the server is currently processing.
|
11
|
+
def self.num_active_requests
|
12
|
+
@@num_active_requests.value
|
13
|
+
end
|
14
|
+
|
15
|
+
# Prevents the server from accepting new requests. Any new requests will return an HTTP
|
16
|
+
# 503 status.
|
17
|
+
def self.block_requests!
|
18
|
+
@@allowing_requests.reset
|
19
|
+
end
|
20
|
+
|
21
|
+
# Allows the server to accept requests again.
|
22
|
+
def self.allow_requests!
|
23
|
+
@@allowing_requests.set
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(app)
|
27
|
+
@app = app
|
28
|
+
end
|
29
|
+
|
30
|
+
def call(env)
|
31
|
+
increment_active_requests
|
32
|
+
if block_requests?
|
33
|
+
block_request(env)
|
34
|
+
else
|
35
|
+
@app.call(env)
|
36
|
+
end
|
37
|
+
ensure
|
38
|
+
decrement_active_requests
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
|
43
|
+
def self.wait_for_no_active_requests(max_wait_time: 10, for_example: nil, diagnostic_log: true)
|
44
|
+
RackRequestBlocker.block_requests!
|
45
|
+
|
46
|
+
unless num_active_requests == 0 || diagnostic_log == false
|
47
|
+
msg = "Waiting on #{num_active_requests} active requests"
|
48
|
+
msg += " for #{for_example.location}" if for_example
|
49
|
+
log msg
|
50
|
+
end
|
51
|
+
|
52
|
+
obtained = (num_active_requests == 0) || @@allowing_requests.wait(max_wait_time)
|
53
|
+
unless obtained
|
54
|
+
raise Timeout::Error, "rack_request_blocker gave up waiting #{max_wait_time}s for pending AJAX requests complete"
|
55
|
+
end
|
56
|
+
ensure
|
57
|
+
RackRequestBlocker.allow_requests!
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def self.log(msg)
|
63
|
+
$stderr.puts "\nrack_request_blocker: #{msg}"
|
64
|
+
end
|
65
|
+
|
66
|
+
def block_requests?
|
67
|
+
! @@allowing_requests.set?
|
68
|
+
end
|
69
|
+
|
70
|
+
def block_request(env)
|
71
|
+
[503, {}, ['Blocked by rack_request_blocker, requests blocked while waiting for in-progress requests to complete']]
|
72
|
+
end
|
73
|
+
|
74
|
+
def increment_active_requests
|
75
|
+
@@num_active_requests.increment
|
76
|
+
end
|
77
|
+
|
78
|
+
def decrement_active_requests
|
79
|
+
new_value = @@num_active_requests.decrement
|
80
|
+
if new_value == 0
|
81
|
+
RackRequestBlocker.allow_requests!
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'rack_request_blocker/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "rack_request_blocker"
|
8
|
+
spec.version = RackRequestBlocker::VERSION
|
9
|
+
spec.authors = ["Jonathan Rochkind"]
|
10
|
+
spec.email = ["jonathan@dnil.net"]
|
11
|
+
|
12
|
+
spec.summary = %q{Rack middleware to let you wait for pending feature test actions to finish}
|
13
|
+
spec.description = %q{Handle race conditions in Rails JS feature tests caused by outstanding overlapping actions in your app, with rack middleware to let you wait for pending feature test actions to finish}
|
14
|
+
spec.homepage = "https://github.com/friendsoftheweb/rack_request_blocker"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
|
25
|
+
spec.add_dependency "concurrent-ruby", "~> 1.0"
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack_request_blocker
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jonathan Rochkind
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-03-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.11'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.11'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: concurrent-ruby
|
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'
|
55
|
+
description: Handle race conditions in Rails JS feature tests caused by outstanding
|
56
|
+
overlapping actions in your app, with rack middleware to let you wait for pending
|
57
|
+
feature test actions to finish
|
58
|
+
email:
|
59
|
+
- jonathan@dnil.net
|
60
|
+
executables: []
|
61
|
+
extensions: []
|
62
|
+
extra_rdoc_files: []
|
63
|
+
files:
|
64
|
+
- ".gitignore"
|
65
|
+
- ".travis.yml"
|
66
|
+
- Gemfile
|
67
|
+
- LICENSE.txt
|
68
|
+
- README.md
|
69
|
+
- Rakefile
|
70
|
+
- bin/console
|
71
|
+
- bin/setup
|
72
|
+
- lib/rack_request_blocker.rb
|
73
|
+
- lib/rack_request_blocker/version.rb
|
74
|
+
- rack_request_blocker.gemspec
|
75
|
+
homepage: https://github.com/friendsoftheweb/rack_request_blocker
|
76
|
+
licenses:
|
77
|
+
- MIT
|
78
|
+
metadata: {}
|
79
|
+
post_install_message:
|
80
|
+
rdoc_options: []
|
81
|
+
require_paths:
|
82
|
+
- lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
requirements: []
|
94
|
+
rubyforge_project:
|
95
|
+
rubygems_version: 2.5.1
|
96
|
+
signing_key:
|
97
|
+
specification_version: 4
|
98
|
+
summary: Rack middleware to let you wait for pending feature test actions to finish
|
99
|
+
test_files: []
|