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.
@@ -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
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rack_request_blocker.gemspec
4
+ gemspec
@@ -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.
@@ -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
+
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -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
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -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,3 @@
1
+ class RackRequestBlocker
2
+ VERSION = "1.0.0"
3
+ 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: []