derail_specs 0.2.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/.rspec +3 -0
- data/.rubocop.yml +25 -0
- data/.rubocop_todo.yml +47 -0
- data/.tool-versions +1 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +122 -0
- data/LICENSE.txt +21 -0
- data/README.md +85 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/derail_specs.gemspec +34 -0
- data/example/.gitignore +23 -0
- data/example/.ruby-version +1 -0
- data/example/Gemfile +15 -0
- data/example/Gemfile.lock +159 -0
- data/example/README.md +24 -0
- data/example/Rakefile +8 -0
- data/example/app/controllers/application_controller.rb +4 -0
- data/example/app/models/application_record.rb +5 -0
- data/example/app/views/layouts/application.html.erb +15 -0
- data/example/bin/bundle +118 -0
- data/example/bin/rails +6 -0
- data/example/bin/rake +6 -0
- data/example/bin/setup +35 -0
- data/example/config/application.rb +40 -0
- data/example/config/boot.rb +5 -0
- data/example/config/credentials.yml.enc +1 -0
- data/example/config/database.yml +25 -0
- data/example/config/environment.rb +7 -0
- data/example/config/environments/development.rb +62 -0
- data/example/config/environments/production.rb +98 -0
- data/example/config/environments/test.rb +51 -0
- data/example/config/initializers/application_controller_renderer.rb +9 -0
- data/example/config/initializers/backtrace_silencers.rb +10 -0
- data/example/config/initializers/content_security_policy.rb +29 -0
- data/example/config/initializers/cookies_serializer.rb +7 -0
- data/example/config/initializers/derail_specs.rb +3 -0
- data/example/config/initializers/filter_parameter_logging.rb +8 -0
- data/example/config/initializers/inflections.rb +17 -0
- data/example/config/initializers/mime_types.rb +5 -0
- data/example/config/initializers/permissions_policy.rb +12 -0
- data/example/config/initializers/wrap_parameters.rb +16 -0
- data/example/config/locales/en.yml +33 -0
- data/example/config/puma.rb +45 -0
- data/example/config/routes.rb +5 -0
- data/example/config.ru +8 -0
- data/example/public/404.html +67 -0
- data/example/public/422.html +67 -0
- data/example/public/500.html +66 -0
- data/example/public/apple-touch-icon-precomposed.png +0 -0
- data/example/public/apple-touch-icon.png +0 -0
- data/example/public/favicon.ico +0 -0
- data/example/public/robots.txt +1 -0
- data/example/tests.sh +4 -0
- data/lib/derail_specs/boot.rb +28 -0
- data/lib/derail_specs/railtie.rb +7 -0
- data/lib/derail_specs/server/app.rb +15 -0
- data/lib/derail_specs/server/checker.rb +43 -0
- data/lib/derail_specs/server/middleware.rb +67 -0
- data/lib/derail_specs/server/puma.rb +32 -0
- data/lib/derail_specs/server/timer.rb +20 -0
- data/lib/derail_specs/server.rb +117 -0
- data/lib/derail_specs/transaction.rb +84 -0
- data/lib/derail_specs/version.rb +5 -0
- data/lib/derail_specs.rb +24 -0
- data/lib/generators/derail_specs/install_generator.rb +16 -0
- data/lib/generators/templates/config/initializers/derail_specs.rb +5 -0
- data/lib/tasks/derail_specs.rake +5 -0
- metadata +145 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
module DerailSpecs
|
2
|
+
class Server
|
3
|
+
module Puma
|
4
|
+
def self.create(app, port, host)
|
5
|
+
require "rack/handler/puma"
|
6
|
+
|
7
|
+
# If we just run the Puma Rack handler it installs signal handlers which prevent us from being able to interrupt tests.
|
8
|
+
# Therefore construct and run the Server instance ourselves.
|
9
|
+
# Rack::Handler::Puma.run(app, { Host: host, Port: port, Threads: "0:4", workers: 0, daemon: false }.merge(options))
|
10
|
+
default_options = { Host: host, Port: port, Threads: "0:4", workers: 0, daemon: false }
|
11
|
+
options = default_options # .merge(options)
|
12
|
+
|
13
|
+
conf = Rack::Handler::Puma.config(app, options)
|
14
|
+
conf.clamp
|
15
|
+
events = ::Puma::Events.stdio
|
16
|
+
|
17
|
+
puma_ver = Gem::Version.new(::Puma::Const::PUMA_VERSION)
|
18
|
+
require_relative "patches/puma_ssl" if (Gem::Version.new("4.0.0")...Gem::Version.new("4.1.0")).cover? puma_ver
|
19
|
+
|
20
|
+
events.log "Starting Puma..."
|
21
|
+
events.log "* Version #{::Puma::Const::PUMA_VERSION} , codename: #{::Puma::Const::CODE_NAME}"
|
22
|
+
events.log "* Min threads: #{conf.options[:min_threads]}, max threads: #{conf.options[:max_threads]}"
|
23
|
+
|
24
|
+
::Puma::Server.new(conf.app, events, conf.options).tap do |s|
|
25
|
+
s.binder.parse conf.options[:binds], s.events
|
26
|
+
s.min_threads = conf.options[:min_threads]
|
27
|
+
s.max_threads = conf.options[:max_threads]
|
28
|
+
end.run.join
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module DerailSpecs
|
2
|
+
class Server
|
3
|
+
class Timer
|
4
|
+
def initialize(expire_in)
|
5
|
+
@start = current
|
6
|
+
@expire_in = expire_in
|
7
|
+
end
|
8
|
+
|
9
|
+
def expired?
|
10
|
+
current - @start >= @expire_in
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def current
|
16
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "uri"
|
4
|
+
require "net/http"
|
5
|
+
require "rack"
|
6
|
+
require_relative "server/middleware"
|
7
|
+
require_relative "server/checker"
|
8
|
+
require_relative "server/timer"
|
9
|
+
require_relative "server/puma"
|
10
|
+
require_relative "server/app"
|
11
|
+
|
12
|
+
# Tons of this server configuration stuff is copied from:
|
13
|
+
# https://github.com/testdouble/cypress-rails
|
14
|
+
module DerailSpecs
|
15
|
+
class Server
|
16
|
+
class << self
|
17
|
+
def ports
|
18
|
+
@ports ||= {}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :app, :host, :port
|
23
|
+
|
24
|
+
def initialize(reportable_errors: [Exception], extra_middleware: [])
|
25
|
+
@app = Server::App
|
26
|
+
@extra_middleware = extra_middleware
|
27
|
+
@server_thread = nil # suppress warnings
|
28
|
+
@host = DerailSpecs.configuration.host
|
29
|
+
@reportable_errors = reportable_errors
|
30
|
+
@port = DerailSpecs.configuration.port
|
31
|
+
@port ||= Server.ports[port_key]
|
32
|
+
@port ||= find_available_port(host)
|
33
|
+
@checker = Checker.new(@host, @port)
|
34
|
+
end
|
35
|
+
|
36
|
+
def reset_error!
|
37
|
+
middleware.clear_error
|
38
|
+
end
|
39
|
+
|
40
|
+
def error
|
41
|
+
middleware.error
|
42
|
+
end
|
43
|
+
|
44
|
+
def using_ssl?
|
45
|
+
@checker.ssl?
|
46
|
+
end
|
47
|
+
|
48
|
+
def responsive?
|
49
|
+
return false if @server_thread&.join(0)
|
50
|
+
|
51
|
+
res = @checker.request { |http| http.get("/__identify__") }
|
52
|
+
|
53
|
+
return res.body == app.object_id.to_s if res.is_a?(Net::HTTPSuccess) || res.is_a?(Net::HTTPRedirection)
|
54
|
+
rescue SystemCallError, Net::ReadTimeout, OpenSSL::SSL::SSLError
|
55
|
+
false
|
56
|
+
end
|
57
|
+
|
58
|
+
def wait_for_pending_requests
|
59
|
+
timer = Timer.new(60)
|
60
|
+
while pending_requests?
|
61
|
+
raise "Requests did not finish in 60 seconds: #{middleware.pending_requests}" if timer.expired?
|
62
|
+
|
63
|
+
sleep 0.01
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def boot
|
68
|
+
unless responsive?
|
69
|
+
Server.ports[port_key] = port
|
70
|
+
|
71
|
+
@server_thread = Thread.new do
|
72
|
+
Puma.create(middleware, port, host)
|
73
|
+
end
|
74
|
+
|
75
|
+
timer = Timer.new(60)
|
76
|
+
until responsive?
|
77
|
+
raise "Rack application timed out during boot" if timer.expired?
|
78
|
+
|
79
|
+
@server_thread.join(0.1)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
self
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def middleware
|
89
|
+
@middleware ||= Middleware.new(app, @reportable_errors, @extra_middleware)
|
90
|
+
end
|
91
|
+
|
92
|
+
def port_key
|
93
|
+
app.object_id # as opposed to middleware.object_id if multiple instances
|
94
|
+
end
|
95
|
+
|
96
|
+
def pending_requests?
|
97
|
+
middleware.pending_requests?
|
98
|
+
end
|
99
|
+
|
100
|
+
def find_available_port(host)
|
101
|
+
server = TCPServer.new(host, 0)
|
102
|
+
port = server.addr[1]
|
103
|
+
server.close
|
104
|
+
|
105
|
+
# Workaround issue where some platforms (mac, ???) when passed a host
|
106
|
+
# of '0.0.0.0' will return a port that is only available on one of the
|
107
|
+
# ip addresses that resolves to, but the next binding to that port requires
|
108
|
+
# that port to be available on all ips
|
109
|
+
server = TCPServer.new(host, port)
|
110
|
+
port
|
111
|
+
rescue Errno::EADDRINUSE
|
112
|
+
retry
|
113
|
+
ensure
|
114
|
+
server&.close
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module DerailSpecs
|
2
|
+
class Transaction
|
3
|
+
def self.instance
|
4
|
+
@instance ||= new
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.begin
|
8
|
+
instance.begin
|
9
|
+
end
|
10
|
+
|
11
|
+
def begin
|
12
|
+
@connections = gather_connections
|
13
|
+
@connections.each do |connection|
|
14
|
+
connection.begin_transaction joinable: false, _lazy: false
|
15
|
+
connection.pool.lock_thread = true
|
16
|
+
end
|
17
|
+
|
18
|
+
# When connections are established in the future, begin a transaction too
|
19
|
+
@connection_subscriber = ActiveSupport::Notifications.subscribe("!connection.active_record") do |_, _, _, _, payload|
|
20
|
+
if payload.key?(:spec_name) && (spec_name = payload[:spec_name])
|
21
|
+
setup_shared_connection_pool
|
22
|
+
|
23
|
+
begin
|
24
|
+
ActiveRecord::Base.connection_handler.retrieve_connection(spec_name)
|
25
|
+
rescue ActiveRecord::ConnectionNotEstablished
|
26
|
+
connection = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
if connection && !@connections.include?(connection)
|
30
|
+
connection.begin_transaction joinable: false, _lazy: false
|
31
|
+
connection.pool.lock_thread = true
|
32
|
+
@connections << connection
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.rollback
|
39
|
+
instance.rollback
|
40
|
+
end
|
41
|
+
|
42
|
+
def rollback
|
43
|
+
return unless @connections.present?
|
44
|
+
|
45
|
+
ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber
|
46
|
+
|
47
|
+
@connections.each do |connection|
|
48
|
+
connection.rollback_transaction if connection.transaction_open?
|
49
|
+
connection.pool.lock_thread = false
|
50
|
+
end
|
51
|
+
@connections.clear
|
52
|
+
|
53
|
+
ActiveRecord::Base.clear_active_connections!
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.reset
|
57
|
+
instance.reset
|
58
|
+
end
|
59
|
+
|
60
|
+
def reset
|
61
|
+
rollback
|
62
|
+
self.begin
|
63
|
+
end
|
64
|
+
|
65
|
+
def gather_connections
|
66
|
+
setup_shared_connection_pool
|
67
|
+
|
68
|
+
ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Shares the writing connection pool with connections on
|
72
|
+
# other handlers.
|
73
|
+
#
|
74
|
+
# In an application with a primary and replica the test fixtures
|
75
|
+
# need to share a connection pool so that the reading connection
|
76
|
+
# can see data in the open transaction on the writing connection.
|
77
|
+
def setup_shared_connection_pool
|
78
|
+
@legacy_saved_pool_configs ||= Hash.new { |hash, key| hash[key] = {} }
|
79
|
+
@saved_pool_configs ||= Hash.new { |hash, key| hash[key] = {} }
|
80
|
+
|
81
|
+
ActiveRecord::TestFixtures.instance_method(:setup_shared_connection_pool).bind_call(self)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
data/lib/derail_specs.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "derail_specs/version"
|
4
|
+
|
5
|
+
module DerailSpecs
|
6
|
+
class Error < StandardError; end
|
7
|
+
|
8
|
+
def self.configuration
|
9
|
+
@configuration ||= Struct
|
10
|
+
.new(:command, :host, :port, keyword_init: true)
|
11
|
+
.new(
|
12
|
+
host: '127.0.0.1',
|
13
|
+
port: 3001,
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.configure
|
18
|
+
yield(configuration)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'derail_specs/boot'
|
23
|
+
require 'derail_specs/transaction'
|
24
|
+
require 'derail_specs/railtie'
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
module DerailSpecs
|
4
|
+
module Generators
|
5
|
+
class InstallGenerator < Rails::Generators::Base
|
6
|
+
source_root File.expand_path("../templates", __dir__)
|
7
|
+
|
8
|
+
def copy_config
|
9
|
+
copy_file(
|
10
|
+
"config/initializers/derail_specs.rb",
|
11
|
+
"config/initializers/derail_specs.rb",
|
12
|
+
)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: derail_specs
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alex Piechowski
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-09-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: puma
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 3.8.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 3.8.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: railties
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 5.2.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 5.2.0
|
41
|
+
description:
|
42
|
+
email:
|
43
|
+
- alex@piechowski.io
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".rspec"
|
49
|
+
- ".rubocop.yml"
|
50
|
+
- ".rubocop_todo.yml"
|
51
|
+
- ".tool-versions"
|
52
|
+
- CHANGELOG.md
|
53
|
+
- CODE_OF_CONDUCT.md
|
54
|
+
- Gemfile
|
55
|
+
- Gemfile.lock
|
56
|
+
- LICENSE.txt
|
57
|
+
- README.md
|
58
|
+
- Rakefile
|
59
|
+
- bin/console
|
60
|
+
- bin/setup
|
61
|
+
- derail_specs.gemspec
|
62
|
+
- example/.gitignore
|
63
|
+
- example/.ruby-version
|
64
|
+
- example/Gemfile
|
65
|
+
- example/Gemfile.lock
|
66
|
+
- example/README.md
|
67
|
+
- example/Rakefile
|
68
|
+
- example/app/controllers/application_controller.rb
|
69
|
+
- example/app/models/application_record.rb
|
70
|
+
- example/app/views/layouts/application.html.erb
|
71
|
+
- example/bin/bundle
|
72
|
+
- example/bin/rails
|
73
|
+
- example/bin/rake
|
74
|
+
- example/bin/setup
|
75
|
+
- example/config.ru
|
76
|
+
- example/config/application.rb
|
77
|
+
- example/config/boot.rb
|
78
|
+
- example/config/credentials.yml.enc
|
79
|
+
- example/config/database.yml
|
80
|
+
- example/config/environment.rb
|
81
|
+
- example/config/environments/development.rb
|
82
|
+
- example/config/environments/production.rb
|
83
|
+
- example/config/environments/test.rb
|
84
|
+
- example/config/initializers/application_controller_renderer.rb
|
85
|
+
- example/config/initializers/backtrace_silencers.rb
|
86
|
+
- example/config/initializers/content_security_policy.rb
|
87
|
+
- example/config/initializers/cookies_serializer.rb
|
88
|
+
- example/config/initializers/derail_specs.rb
|
89
|
+
- example/config/initializers/filter_parameter_logging.rb
|
90
|
+
- example/config/initializers/inflections.rb
|
91
|
+
- example/config/initializers/mime_types.rb
|
92
|
+
- example/config/initializers/permissions_policy.rb
|
93
|
+
- example/config/initializers/wrap_parameters.rb
|
94
|
+
- example/config/locales/en.yml
|
95
|
+
- example/config/puma.rb
|
96
|
+
- example/config/routes.rb
|
97
|
+
- example/public/404.html
|
98
|
+
- example/public/422.html
|
99
|
+
- example/public/500.html
|
100
|
+
- example/public/apple-touch-icon-precomposed.png
|
101
|
+
- example/public/apple-touch-icon.png
|
102
|
+
- example/public/favicon.ico
|
103
|
+
- example/public/robots.txt
|
104
|
+
- example/tests.sh
|
105
|
+
- lib/derail_specs.rb
|
106
|
+
- lib/derail_specs/boot.rb
|
107
|
+
- lib/derail_specs/railtie.rb
|
108
|
+
- lib/derail_specs/server.rb
|
109
|
+
- lib/derail_specs/server/app.rb
|
110
|
+
- lib/derail_specs/server/checker.rb
|
111
|
+
- lib/derail_specs/server/middleware.rb
|
112
|
+
- lib/derail_specs/server/puma.rb
|
113
|
+
- lib/derail_specs/server/timer.rb
|
114
|
+
- lib/derail_specs/transaction.rb
|
115
|
+
- lib/derail_specs/version.rb
|
116
|
+
- lib/generators/derail_specs/install_generator.rb
|
117
|
+
- lib/generators/templates/config/initializers/derail_specs.rb
|
118
|
+
- lib/tasks/derail_specs.rake
|
119
|
+
homepage: https://github.com/roshreview/derail_specs
|
120
|
+
licenses:
|
121
|
+
- MIT
|
122
|
+
metadata:
|
123
|
+
homepage_uri: https://github.com/roshreview/derail_specs
|
124
|
+
source_code_uri: https://github.com/roshreview/derail_specs
|
125
|
+
changelog_uri: https://github.com/roshreview/derail_specs/blob/main/CHANGELOG.md
|
126
|
+
post_install_message:
|
127
|
+
rdoc_options: []
|
128
|
+
require_paths:
|
129
|
+
- lib
|
130
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - ">="
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: 2.6.0
|
135
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '0'
|
140
|
+
requirements: []
|
141
|
+
rubygems_version: 3.2.22
|
142
|
+
signing_key:
|
143
|
+
specification_version: 4
|
144
|
+
summary: Rails test server for external tests.
|
145
|
+
test_files: []
|