cypress-rails 0.8.0.rc1 → 0.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 340bff5215842cc3afb419ff29bef8b977e573fdf5311711b4d0badaa05104d1
4
- data.tar.gz: c720c43244302da09c9f63f416cba150926c0a6ef4cbf53bc2c418727dd3b87a
3
+ metadata.gz: c08ef4f9087d66f0f04df9831568414e757aae3fb83baf6bfd85c0d7a3509312
4
+ data.tar.gz: aa4baeebd6d8035ca9b9fc6ba91b0c487e9cf3a385a25ee9da92c96e2fd4ce2c
5
5
  SHA512:
6
- metadata.gz: bfb0b42e92ad4e5b576264b18ba035964823cc32b3717979edd127ca14b17f14639cf14083390c27daa1740b0302c0ba9c8b1ba53bb531ca72a03bdd9e9e0798
7
- data.tar.gz: 78f57627a4eec8c0ccde58b24b1e6104fe8164e11375a46561a5cf8fbcce12a9536041e60c7be431a7c389a00b3132db679a13b93f49810069e8a6271f255864
6
+ metadata.gz: 15cf818dd04eb1eb89853d2a14839dd54d25ed4c39b21a705fcae507c7cecb12813477913fab43e486a7f80f0bcc4df8cb18cafa0252d3a38c33608b8441c6b7
7
+ data.tar.gz: 2fa87db917ccc275d14c8dc28f67a55c13a375e13d6a9f25887fc9ad87989d18bb515b2eabab19ef58dcee6da9b098cf1ccabf97ee6a057649468d8f4f740245
@@ -3,24 +3,88 @@ name: CI
3
3
  on: [push, pull_request, workflow_dispatch]
4
4
 
5
5
  jobs:
6
- build:
6
+ unit-tests:
7
7
  runs-on: ubuntu-latest
8
8
 
9
9
  strategy:
10
+ fail-fast: false
10
11
  matrix:
11
- ruby-version: ['3.0', '3.3']
12
- gemfile: ['Gemfile', 'Gemfile_rails71']
12
+ ruby-version: ['3.0', '3.1', '3.2', '3.3', '3.4']
13
+
14
+ steps:
15
+ - uses: actions/checkout@v7
16
+ - uses: ruby/setup-ruby@v1
17
+ with:
18
+ ruby-version: ${{ matrix.ruby-version }}
19
+ rubygems: latest
20
+ bundler: latest
21
+ bundler-cache: true
22
+
23
+ - name: Run root unit tests and lint
24
+ run: |
25
+ bundle
26
+ bundle exec rake
27
+
28
+ example-app-tests:
29
+ name: example-app-tests (${{ matrix.ruby-version }}, ${{ matrix.rails-version }}, transactional ${{ matrix.transactional-server }})
30
+ runs-on: ubuntu-latest
31
+
32
+ strategy:
33
+ fail-fast: false
34
+ matrix:
35
+ ruby-version: ['3.0', '3.1', '3.2', '3.3', '3.4']
36
+ rails-version: ['7.1', '7.2', '8.0', '8.1']
37
+ transactional-server: ['true']
13
38
  exclude:
14
- # Rails 7.2 requires Ruby 3.1+
15
39
  - ruby-version: '3.0'
16
- gemfile: 'Gemfile'
40
+ rails-version: '7.2'
41
+ - ruby-version: '3.0'
42
+ rails-version: '8.0'
43
+ - ruby-version: '3.0'
44
+ rails-version: '8.1'
45
+ - ruby-version: '3.1'
46
+ rails-version: '8.0'
47
+ - ruby-version: '3.1'
48
+ rails-version: '8.1'
49
+ include:
50
+ - ruby-version: '3.4'
51
+ rails-version: '7.1'
52
+ transactional-server: 'false'
53
+ - ruby-version: '3.4'
54
+ rails-version: '7.2'
55
+ transactional-server: 'false'
56
+ - ruby-version: '3.4'
57
+ rails-version: '8.0'
58
+ transactional-server: 'false'
59
+
60
+ services:
61
+ postgres:
62
+ image: postgres:18
63
+ env:
64
+ POSTGRES_PASSWORD: postgres
65
+ ports:
66
+ - 5432:5432
67
+ options: >-
68
+ --health-cmd pg_isready
69
+ --health-interval 10s
70
+ --health-timeout 5s
71
+ --health-retries 5
72
+
73
+ env:
74
+ BUNDLE_GEMFILE: ${{ github.workspace }}/example/gemfiles/rails_${{ matrix.rails-version }}.gemfile
75
+ CYPRESS_RAILS_TRANSACTIONAL_SERVER: ${{ matrix.transactional-server }}
76
+ PGHOST: localhost
77
+ PGUSER: postgres
78
+ PGPASSWORD: postgres
17
79
 
18
80
  steps:
19
- - uses: actions/checkout@v4
81
+ - uses: actions/checkout@v7
82
+ - uses: actions/setup-node@v7
20
83
  - uses: ruby/setup-ruby@v1
21
84
  with:
22
85
  ruby-version: ${{ matrix.ruby-version }}
23
86
  bundler-cache: true
87
+ cache-version: rails-${{ matrix.rails-version }}
24
88
 
25
- - name: Run tests
26
- run: ./script/test ${{matrix.gemfile}}
89
+ - name: Run example app Cypress tests
90
+ run: ./script/test_example_app
data/.gitignore CHANGED
@@ -6,3 +6,9 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+
10
+ # Gemfile.lock files are Ruby-version-sensitive (transitive deps can pin
11
+ # versions requiring a newer Ruby than some cells of the CI matrix), so each
12
+ # job resolves and caches its own instead of sharing a committed lock.
13
+ /Gemfile.lock
14
+ /example/gemfiles/*.gemfile.lock
data/CHANGELOG.md CHANGED
@@ -1,13 +1,46 @@
1
1
  # CHANGELOG
2
2
 
3
- ## 0.8.0.rc1
4
- * Add compatibility with Rails 7.2
3
+ ## 0.9.0
4
+
5
+ * **[Breaking]** Require Rails 7.1 or newer
6
+ * **[Breaking]** (relative to 0.8.0) Rename `after_reset_requested` back to
7
+ `after_state_reset`, and restore the transactional server as the default
8
+ behavior
9
+ * Bring back the transactional server (and `CYPRESS_RAILS_TRANSACTIONAL_SERVER`),
10
+ which 0.8.0 had removed. It didn't work reliably to have apps reset their own
11
+ state under cypress-rails' multi-threaded Puma server
12
+ * `/cypress_rails_reset_state` now resets before responding (returning `200`
13
+ instead of `202`), rather than deferring the reset to the next request
14
+
15
+ See [docs/changes-in-0.9.0.md](docs/changes-in-0.9.0.md) for a full comparison
16
+ with both 0.7.1 and 0.8.0.
17
+
18
+ ## 0.8.0
19
+
20
+ (Avoid this version due to issues with resetting data during threaded requests.
21
+ 0.9.0 is almost entirely compatible with 0.7.1 while still bringing Rails 7.2+
22
+ compatibility, so the breaking changes in this version are for nothing.)
23
+
24
+ * **[Breaking]** Remove `CYPRESS_RAILS_TRANSACTIONAL_SERVER`. cypress-rails no
25
+ longer wraps the server in a transaction or resets it between tests -
26
+ automatic Rails-version-specific transaction management proved unsustainable
27
+ to maintain (see [#164](https://github.com/testdouble/cypress-rails/issues/164),
28
+ [#161](https://github.com/testdouble/cypress-rails/issues/161)). Apps must
29
+ now reset their own state (e.g. with `database_cleaner`) using the
30
+ `after_reset_requested` hook; see the example app for a working setup
31
+ * **[Breaking]** Remove the `after_transaction_start` hook, since cypress-rails
32
+ no longer manages transactions
33
+ * **[Breaking]** Rename the `after_state_reset` hook to `after_reset_requested`.
34
+ It now fires whenever a reset was requested (via hitting
35
+ `/cypress_rails_reset_state`), rather than after cypress-rails had already
36
+ performed one - the app is expected to perform the reset itself in this hook
37
+ * **[Breaking]** Remove `CypressRails::Config#transactional_server`
38
+ * Add Rails 7.2 / 8.0 support
5
39
 
6
40
  ## 0.7.1
7
41
  * Add Rack 3.1 support [#163](https://github.com/testdouble/cypress-rails/pull/163)
8
42
 
9
43
  ## 0.7.0
10
- git
11
44
  * Add a `CYPRESS_RAILS_CYPRESS_DIR` option for cases where
12
45
  the cypress tests live outside the CYPRESS_RAILS_DIR [#159](https://github.com/testdouble/cypress-rails/pull/159)
13
46
 
data/README.md CHANGED
@@ -93,6 +93,11 @@ is a JavaScript-based tool and can't easily manipulate your Rails app directly,
93
93
  cypress-rails provides a number of hooks that you can use to manage your test
94
94
  data.
95
95
 
96
+ By default, cypress-rails runs your app inside a database transaction and rolls
97
+ it back between tests, much like a Rails system test. Every thread serving your
98
+ app shares that one transaction, so whatever your tests create is undone on the
99
+ next reset.
100
+
96
101
  Here's what a `config/initializers/cypress_rails.rb` initializer might look
97
102
  like:
98
103
 
@@ -107,6 +112,10 @@ CypressRails.hooks.after_transaction_start do
107
112
  # Called after the transaction is started (at launch and after each reset)
108
113
  end
109
114
 
115
+ CypressRails.hooks.after_server_start do
116
+ # Called once, after the server has booted
117
+ end
118
+
110
119
  CypressRails.hooks.after_state_reset do
111
120
  # Triggered after `/cypress_rails_reset_state` is called
112
121
  end
@@ -122,16 +131,18 @@ initializer](/example/config/initializers/cypress_rails_initializer.rb)
122
131
  in this repo.)
123
132
 
124
133
  The gem also provides a special route on the test server:
125
- `/cypress_rails_reset_state`. Each time it's called, cypress-rails will do
126
- two things at the beginning of the next request received by the Rails app:
127
-
128
- * If `CYPRESS_RAILS_TRANSACTIONAL_SERVER` is enabled, roll back the transaction,
129
- effectively resetting the application state to whatever it was at the start of
130
- the test run
134
+ `/cypress_rails_reset_state`. Each time it's called, cypress-rails does two
135
+ things before responding:
131
136
 
137
+ * If `CYPRESS_RAILS_TRANSACTIONAL_SERVER` is enabled, roll back the transaction
138
+ and start a new one, resetting the database to how it was when the test run
139
+ started, then trigger any `after_transaction_start` hooks
132
140
  * Trigger any `after_state_reset` hooks you've configured (regardless of the
133
141
  transactional server setting)
134
142
 
143
+ If a hook raises an error, the request fails with a 500 status and the error
144
+ message, so the `cy.request` that triggered the reset fails too.
145
+
135
146
  This way, you can easily instruct the server to reset its test state from your
136
147
  Cypress tests like so:
137
148
 
@@ -144,6 +155,39 @@ beforeEach(() => {
144
155
  (Remember, in Cypress, `before` is a before-all hook and `beforeEach` is run
145
156
  between each test case!)
146
157
 
158
+ ### Turning off the transactional server
159
+
160
+ The transactional server funnels all of your app's database queries through a
161
+ single connection, one at a time, just like a Rails system test. If your tests
162
+ need your app to access the database concurrently, or your app does work that
163
+ can't happen inside a transaction, set `CYPRESS_RAILS_TRANSACTIONAL_SERVER=false`
164
+ and reset your data yourself in an `after_state_reset` hook. For example, with
165
+ [database_cleaner](https://github.com/DatabaseCleaner/database_cleaner) and
166
+ Rails fixtures:
167
+
168
+ ```ruby
169
+ CypressRails.hooks.after_state_reset do
170
+ DatabaseCleaner[:active_record].clean_with(:truncation)
171
+ ActiveRecord::FixtureSet.reset_cache
172
+ ActiveRecord::FixtureSet.create_fixtures(Rails.root.join("test/fixtures"), ["users", "posts"])
173
+ end
174
+ ```
175
+
176
+ A few things to watch out for:
177
+
178
+ * Don't use database_cleaner's `:transaction` strategy. It only opens a
179
+ transaction on one of the server's database connections, so requests served
180
+ on other connections save their changes for real, and those changes survive
181
+ the reset. (On SQLite, the open transaction also makes other connections'
182
+ writes fail with "database is locked" errors.)
183
+ * Don't set `DatabaseCleaner.strategy = ...` at the top of an initializer.
184
+ Before ActiveRecord has loaded, that assignment is silently ignored and
185
+ database_cleaner's default `:transaction` strategy is used instead.
186
+ * Truncation removes everything, including data loaded in
187
+ `before_server_start`, so rebuild whatever your tests need in the hook. Call
188
+ `ActiveRecord::FixtureSet.reset_cache` before reloading fixtures, or they'll
189
+ be skipped as already loaded.
190
+
147
191
  ## Configuration
148
192
 
149
193
  ### Environment variables
@@ -164,10 +208,12 @@ preferred environment variables project-wide using a tool like
164
208
  requests to the app (e.g. via `cy.visit()`). If you've customized your
165
209
  `baseUrl` setting (e.g. in `cypress.config.js`), you'll need to duplicate it with
166
210
  this environment variable
167
- * **CYPRESS_RAILS_TRANSACTIONAL_SERVER** (default: `true`) when true, will start
168
- a transaction on all database connections before launching the server. In
169
- general this means anything done during `cypress open` or `cypress run` will
170
- be rolled back on exit (similar to running a Rails System test)
211
+ * **CYPRESS_RAILS_TRANSACTIONAL_SERVER** (default: `true`) when true, starts a
212
+ transaction before launching the server and rolls it back on every reset and
213
+ on exit, so anything done during `cypress open` or `cypress run` is undone
214
+ (similar to running a Rails system test). While it's enabled, all of the
215
+ server's database access shares one connection. See [Turning off the
216
+ transactional server](#turning-off-the-transactional-server)
171
217
  * **CYPRESS_RAILS_CYPRESS_OPTS** (default: _none_) any options you want to
172
218
  forward to the Cypress CLI when running its `open` or `run` commands.
173
219
 
@@ -194,35 +240,39 @@ $ CYPRESS_RAILS_CYPRESS_OPTS="--browser chromium" bin/rake cypress:run
194
240
 
195
241
  Pass a block to `CypressRails.hooks.before_server_start` to register a hook that
196
242
  will execute before the server or any transaction has been started. If you use
197
- Rails fixtures, it may make sense to load them here, so they don't need to be
198
- re-inserted for each request
243
+ Rails fixtures, it may make sense to load them here, so they persist across
244
+ resets instead of being re-inserted each time.
199
245
 
200
246
  ### after_server_start
201
247
 
202
248
  Pass a block to `CypressRails.hooks.after_server_start` to register a hook that
203
- will execute after the server has booted.
249
+ will execute after the server has booted (inside the transaction, if
250
+ `CYPRESS_RAILS_TRANSACTIONAL_SERVER` is enabled).
204
251
 
205
252
  ### after_transaction_start
206
253
 
207
254
  If there's any custom behavior or state management you want to do inside the
208
255
  transaction (so that it's also rolled back each time a reset is triggered),
209
- pass a block to `CypressRails.hooks.after_transaction_start`.
256
+ pass a block to `CypressRails.hooks.after_transaction_start`. It runs when the
257
+ server launches and after every reset, and only when
258
+ `CYPRESS_RAILS_TRANSACTIONAL_SERVER` is enabled.
210
259
 
211
260
  ### after_state_reset
212
261
 
213
262
  Every time the test server receives an HTTP request at
214
- `/cypress_rails_reset_state`, the transaction will be rolled back (if
215
- `CYPRESS_RAILS_TRANSACTIONAL_SERVER` is enabled) and the `after_state_reset`
216
- hook will be triggered. To set up the hook, pass a block to
217
- `CypressRails.hooks.after_state_reset`.
263
+ `/cypress_rails_reset_state`, the transaction will be rolled back and restarted
264
+ (if `CYPRESS_RAILS_TRANSACTIONAL_SERVER` is enabled) and then the
265
+ `after_state_reset` hook will be triggered, all before the request responds. To
266
+ set up the hook, pass a block to `CypressRails.hooks.after_state_reset`.
218
267
 
219
268
  ### before_server_stop
220
269
 
221
270
  In case you've made any permanent changes to your test database that could
222
271
  pollute other test suites or scripts, you can use the `before_server_stop` to
223
272
  (assuming everything exits gracefully) clean things up and restore the state
224
- of your test database. To set up the hook, pass a block to
225
- `CypressRails.hooks.before_server_stop`.
273
+ of your test database. If `CYPRESS_RAILS_TRANSACTIONAL_SERVER` is enabled, this
274
+ runs after the transaction has been rolled back. To set up the hook, pass a
275
+ block to `CypressRails.hooks.before_server_stop`.
226
276
 
227
277
  ## Configuring Rails
228
278
 
@@ -16,17 +16,19 @@ Gem::Specification.new do |spec|
16
16
  # Specify which files should be added to the gem when it is released.
17
17
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
18
18
  spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
19
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|example)/}) }
19
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|example|docs)/}) }
20
20
  end
21
21
  spec.bindir = "exe"
22
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
23
  spec.require_paths = ["lib"]
24
24
 
25
- spec.add_dependency "railties", ">= 5.2.0"
25
+ spec.add_dependency "railties", ">= 7.1"
26
26
  spec.add_dependency "puma", ">= 3.8.0"
27
27
 
28
28
  spec.add_development_dependency "bundler"
29
29
  spec.add_development_dependency "rake", "~> 13.0"
30
30
  spec.add_development_dependency "minitest", "~> 5.0"
31
31
  spec.add_development_dependency "standard", ">= 0.2.0"
32
+ spec.add_development_dependency "activerecord"
33
+ spec.add_development_dependency "sqlite3"
32
34
  end
@@ -8,7 +8,6 @@ module CypressRails
8
8
  class LaunchesCypress
9
9
  def initialize
10
10
  @initializer_hooks = InitializerHooks.instance
11
- @manages_transactions = ManagesTransactions.instance
12
11
  @starts_rails_server = StartsRailsServer.new
13
12
  @finds_bin = FindsBin.new
14
13
  end
@@ -17,7 +16,8 @@ module CypressRails
17
16
  puts config
18
17
  @initializer_hooks.run(:before_server_start)
19
18
  if config.transactional_server
20
- @manages_transactions.begin_transaction
19
+ ManagesTransactions.instance.begin_transaction
20
+ @initializer_hooks.run(:after_transaction_start)
21
21
  end
22
22
  server = @starts_rails_server.call(
23
23
  host: config.host,
@@ -53,7 +53,11 @@ module CypressRails
53
53
  return if @at_exit_hooks_have_fired
54
54
 
55
55
  if config.transactional_server
56
- @manages_transactions.rollback_transaction
56
+ begin
57
+ ManagesTransactions.instance.rollback_transaction
58
+ rescue => e
59
+ warn e.full_message
60
+ end
57
61
  end
58
62
  @initializer_hooks.run(:before_server_stop)
59
63
 
@@ -1,82 +1,76 @@
1
- require_relative "initializer_hooks"
2
-
3
1
  module CypressRails
4
2
  class ManagesTransactions
5
3
  def self.instance
6
4
  @instance ||= new
7
5
  end
8
6
 
9
- def begin_transaction
10
- @connections = gather_connections
11
- @connections.each do |connection|
12
- connection.begin_transaction joinable: false, _lazy: false
7
+ module MinitestLifecycle
8
+ def before_setup
13
9
  end
14
10
 
15
- # When connections are established in the future, begin a transaction too
16
- @connection_subscriber = ActiveSupport::Notifications.subscribe("!connection.active_record") { |_, _, _, _, payload|
17
- if payload.key?(:spec_name) && (spec_name = payload[:spec_name])
18
- setup_shared_connection_pool
19
-
20
- begin
21
- connection = ActiveRecord::Base.connection_handler.retrieve_connection(spec_name)
22
- rescue ActiveRecord::ConnectionNotEstablished
23
- connection = nil
24
- end
11
+ def after_teardown
12
+ end
13
+ end
25
14
 
26
- if connection && !@connections.include?(connection)
27
- connection.begin_transaction joinable: false, _lazy: false
28
- @connections << connection
29
- end
30
- end
31
- }
15
+ def begin_transaction
16
+ return unless defined?(ActiveRecord::TestFixtures)
32
17
 
33
- @initializer_hooks.run(:after_transaction_start)
18
+ # A connection this thread already holds wouldn't be the pinned one
19
+ release_connections_held_by_this_thread
20
+ on_owner_thread { fixtures.before_setup }
34
21
  end
35
22
 
36
23
  def rollback_transaction
37
- return unless @connections.present?
24
+ return unless defined?(ActiveRecord::TestFixtures)
38
25
 
39
- ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber
40
-
41
- @connections.each do |connection|
42
- connection.rollback_transaction if connection.transaction_open?
26
+ begin
27
+ on_owner_thread { fixtures.after_teardown }
28
+ ensure
29
+ release_connections_held_by_this_thread
43
30
  end
44
- @connections.clear
45
-
46
- ActiveRecord::Base.connection_handler.clear_active_connections!
47
31
  end
48
32
 
49
33
  private
50
34
 
51
35
  def initialize
52
- @initializer_hooks = InitializerHooks.instance
36
+ @jobs = Queue.new
37
+ # Rails 7.1 keys a pinned connection to the thread that pinned it, and a
38
+ # Puma thread's end-of-request cleanup would check it back in, so a
39
+ # dedicated thread that never serves requests owns the transaction.
40
+ @owner = Thread.new do
41
+ loop do
42
+ job, result = @jobs.pop
43
+ begin
44
+ result << [:ok, job.call]
45
+ rescue Exception => e # standard:disable Lint/RescueException
46
+ result << [:error, e]
47
+ end
48
+ end
49
+ end
53
50
  end
54
51
 
55
- def gather_connections
56
- setup_shared_connection_pool
52
+ def on_owner_thread(&job)
53
+ result = Queue.new
54
+ @jobs << [job, result]
55
+ status, value = result.pop
56
+ raise value if status == :error
57
+ value
58
+ end
57
59
 
58
- ActiveRecord::Base.connection_handler.connection_pool_list.map do |pool|
59
- # Rails 7.2+
60
- if pool.respond_to?(:lease_connection)
61
- pool.lease_connection
62
- else
63
- pool.connection
64
- end
65
- end
60
+ def release_connections_held_by_this_thread
61
+ ActiveRecord::Base.connection_handler.clear_active_connections!(:all)
66
62
  end
67
63
 
68
- # Shares the writing connection pool with connections on
69
- # other handlers.
70
- #
71
- # In an application with a primary and replica the test fixtures
72
- # need to share a connection pool so that the reading connection
73
- # can see data in the open transaction on the writing connection.
74
- def setup_shared_connection_pool
75
- return unless ActiveRecord::TestFixtures.respond_to?(:setup_shared_connection_pool)
76
- @legacy_saved_pool_configs ||= Hash.new { |hash, key| hash[key] = {} }
77
- @saved_pool_configs ||= Hash.new { |hash, key| hash[key] = {} }
64
+ def fixtures
65
+ @fixtures ||= Class.new {
66
+ # Must be included before TestFixtures, or its hooks never run
67
+ include MinitestLifecycle
68
+ include ActiveRecord::TestFixtures
78
69
 
79
- ActiveRecord::TestFixtures.instance_method(:setup_shared_connection_pool).bind(self).call
70
+ def name
71
+ "cypress-rails"
72
+ end
73
+ }.new
80
74
  end
81
75
  end
82
76
  end
@@ -1,20 +1,43 @@
1
1
  require_relative "config"
2
- require_relative "manages_transactions"
3
2
  require_relative "initializer_hooks"
3
+ require_relative "manages_transactions"
4
4
 
5
5
  module CypressRails
6
6
  class ResetsState
7
- def initialize
8
- @manages_transactions = ManagesTransactions.instance
9
- @initializer_hooks = InitializerHooks.instance
7
+ LOCK = Mutex.new
8
+
9
+ def initialize(
10
+ transactional_server:,
11
+ manages_transactions: nil,
12
+ initializer_hooks: InitializerHooks.instance,
13
+ wrap: ->(&block) { Rails.application.reloader.wrap(&block) }
14
+ )
15
+ @transactional_server = transactional_server
16
+ @manages_transactions = manages_transactions
17
+ @initializer_hooks = initializer_hooks
18
+ @wrap = wrap
10
19
  end
11
20
 
12
- def call(transactional_server:)
13
- if transactional_server
14
- @manages_transactions.rollback_transaction
15
- @manages_transactions.begin_transaction
21
+ def call
22
+ LOCK.synchronize do
23
+ if @transactional_server
24
+ # Outside the reloader: waiting on the owner thread while holding a
25
+ # reloader share could deadlock against a code reload
26
+ manages_transactions.rollback_transaction
27
+ manages_transactions.begin_transaction
28
+ end
29
+
30
+ @wrap.call do
31
+ @initializer_hooks.run(:after_transaction_start) if @transactional_server
32
+ @initializer_hooks.run(:after_state_reset)
33
+ end
16
34
  end
17
- @initializer_hooks.run(:after_state_reset)
35
+ end
36
+
37
+ private
38
+
39
+ def manages_transactions
40
+ @manages_transactions ||= ManagesTransactions.instance
18
41
  end
19
42
  end
20
43
  end
@@ -23,7 +23,9 @@ module CypressRails
23
23
  host:,
24
24
  port:,
25
25
  reportable_errors: [Exception],
26
- extra_middleware: [])
26
+ extra_middleware: [],
27
+ initializer_hooks: InitializerHooks.instance,
28
+ puma: Puma)
27
29
  @app = app
28
30
  @extra_middleware = extra_middleware
29
31
  @server_thread = nil # suppress warnings
@@ -33,7 +35,8 @@ module CypressRails
33
35
  @port ||= Server.ports[port_key]
34
36
  @port ||= find_available_port(host)
35
37
  @checker = Checker.new(@host, @port)
36
- @initializer_hooks = InitializerHooks.instance
38
+ @initializer_hooks = initializer_hooks
39
+ @puma = puma
37
40
  end
38
41
 
39
42
  def reset_error!
@@ -72,7 +75,7 @@ module CypressRails
72
75
  Server.ports[port_key] = port
73
76
 
74
77
  @server_thread = Thread.new {
75
- Puma.create(middleware, port, host)
78
+ @puma.create(middleware, port, host)
76
79
  }
77
80
 
78
81
  timer = Timer.new(60)
@@ -80,8 +83,10 @@ module CypressRails
80
83
  raise "Rack application timed out during boot" if timer.expired?
81
84
 
82
85
  @server_thread.join(0.1)
83
- @initializer_hooks.run(:after_server_start)
84
86
  end
87
+
88
+ # Once the server is up, so slow hooks don't count against the timeout
89
+ @initializer_hooks.run(:after_server_start)
85
90
  end
86
91
 
87
92
  self
@@ -1,28 +1,26 @@
1
- require_relative "tracks_resets"
1
+ require_relative "resets_state"
2
2
  require_relative "server"
3
3
 
4
4
  module CypressRails
5
5
  class StartsRailsServer
6
6
  def call(host:, port:, transactional_server:)
7
- configure_rails_to_run_our_state_reset_on_every_request!(transactional_server)
8
- app = create_rack_app
7
+ app = create_rack_app(transactional_server)
9
8
  Server.new(app, host: host, port: port).tap do |server|
10
9
  server.boot
11
10
  end
12
11
  end
13
12
 
14
- def configure_rails_to_run_our_state_reset_on_every_request!(transactional_server)
15
- Rails.application.executor.to_run do
16
- TracksResets.instance.reset_state_if_needed(transactional_server)
17
- end
18
- end
19
-
20
- def create_rack_app
13
+ def create_rack_app(transactional_server)
21
14
  Rack::Builder.new do
22
15
  map "/cypress_rails_reset_state" do
23
16
  run lambda { |env|
24
- TracksResets.instance.reset_needed!
25
- [202, {"Content-Type" => "text/plain"}, ["Accepted"]]
17
+ begin
18
+ ResetsState.new(transactional_server: transactional_server).call
19
+ [200, {"content-type" => "text/plain"}, ["Reset"]]
20
+ rescue => e
21
+ warn e.full_message
22
+ [500, {"content-type" => "text/plain"}, ["#{e.class}: #{e.message}"]]
23
+ end
26
24
  }
27
25
  end
28
26
  map "/" do
@@ -1,3 +1,3 @@
1
1
  module CypressRails
2
- VERSION = "0.8.0.rc1"
2
+ VERSION = "0.9.0"
3
3
  end
data/script/test CHANGED
@@ -6,12 +6,20 @@ set -x
6
6
 
7
7
  echo "---> Installing dependencies"
8
8
  bundle
9
+ cd example
10
+ bundle
11
+ yarn
12
+ cd ..
9
13
 
10
- echo "---> Running tests"
14
+ echo "---> Running root tests"
11
15
  bundle exec rake
12
- BUNDLE_GEMFILE="${1-Gemfile}" ./script/test_example_app
13
16
 
14
- bundle exec rake test
17
+ for gemfile in example/gemfiles/*.gemfile; do
18
+ echo "---> Running example app tests against $gemfile"
19
+ export BUNDLE_GEMFILE="$PWD/$gemfile"
20
+ (cd example && bundle) # ensure that gemfile's lockfile is installed
21
+ ./script/test_example_app
22
+ unset BUNDLE_GEMFILE
23
+ done
15
24
 
16
25
  echo "---> Job's done!"
17
-
@@ -2,6 +2,8 @@
2
2
 
3
3
  set -e
4
4
 
5
+ directory=$1
6
+
5
7
  cd example
6
8
  bundle
7
9
  yarn install
@@ -16,3 +18,4 @@ if ! bundle exec rake cypress:run CYPRESS_RAILS_CYPRESS_OPTS="-h" | grep -q "Usa
16
18
  echo "Failed to pass options to cypress run"
17
19
  exit 1
18
20
  fi
21
+
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cypress-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0.rc1
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Searls
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-09-25 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: railties
@@ -16,14 +15,14 @@ dependencies:
16
15
  requirements:
17
16
  - - ">="
18
17
  - !ruby/object:Gem::Version
19
- version: 5.2.0
18
+ version: '7.1'
20
19
  type: :runtime
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
23
  - - ">="
25
24
  - !ruby/object:Gem::Version
26
- version: 5.2.0
25
+ version: '7.1'
27
26
  - !ruby/object:Gem::Dependency
28
27
  name: puma
29
28
  requirement: !ruby/object:Gem::Requirement
@@ -94,7 +93,34 @@ dependencies:
94
93
  - - ">="
95
94
  - !ruby/object:Gem::Version
96
95
  version: 0.2.0
97
- description:
96
+ - !ruby/object:Gem::Dependency
97
+ name: activerecord
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: sqlite3
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ type: :development
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
98
124
  email:
99
125
  - searls@gmail.com
100
126
  executables:
@@ -108,7 +134,6 @@ files:
108
134
  - ".travis.yml"
109
135
  - CHANGELOG.md
110
136
  - Gemfile
111
- - Gemfile.lock
112
137
  - LICENSE.txt
113
138
  - README.md
114
139
  - Rakefile
@@ -135,7 +160,6 @@ files:
135
160
  - lib/cypress-rails/server/puma.rb
136
161
  - lib/cypress-rails/server/timer.rb
137
162
  - lib/cypress-rails/starts_rails_server.rb
138
- - lib/cypress-rails/tracks_resets.rb
139
163
  - lib/cypress-rails/version.rb
140
164
  - script/test
141
165
  - script/test_example_app
@@ -144,7 +168,6 @@ licenses:
144
168
  - MIT
145
169
  metadata:
146
170
  rubygems_mfa_required: 'true'
147
- post_install_message:
148
171
  rdoc_options: []
149
172
  require_paths:
150
173
  - lib
@@ -159,8 +182,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
159
182
  - !ruby/object:Gem::Version
160
183
  version: '0'
161
184
  requirements: []
162
- rubygems_version: 3.5.11
163
- signing_key:
185
+ rubygems_version: 4.0.21
164
186
  specification_version: 4
165
187
  summary: Helps you write Cypress tests of your Rails app
166
188
  test_files: []
data/Gemfile.lock DELETED
@@ -1,153 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- cypress-rails (0.8.0.rc1)
5
- puma (>= 3.8.0)
6
- railties (>= 5.2.0)
7
-
8
- GEM
9
- remote: https://rubygems.org/
10
- specs:
11
- actionpack (7.1.3)
12
- actionview (= 7.1.3)
13
- activesupport (= 7.1.3)
14
- nokogiri (>= 1.8.5)
15
- racc
16
- rack (>= 2.2.4)
17
- rack-session (>= 1.0.1)
18
- rack-test (>= 0.6.3)
19
- rails-dom-testing (~> 2.2)
20
- rails-html-sanitizer (~> 1.6)
21
- actionview (7.1.3)
22
- activesupport (= 7.1.3)
23
- builder (~> 3.1)
24
- erubi (~> 1.11)
25
- rails-dom-testing (~> 2.2)
26
- rails-html-sanitizer (~> 1.6)
27
- activesupport (7.1.3)
28
- base64
29
- bigdecimal
30
- concurrent-ruby (~> 1.0, >= 1.0.2)
31
- connection_pool (>= 2.2.5)
32
- drb
33
- i18n (>= 1.6, < 2)
34
- minitest (>= 5.1)
35
- mutex_m
36
- tzinfo (~> 2.0)
37
- ast (2.4.2)
38
- base64 (0.2.0)
39
- bigdecimal (3.1.6)
40
- builder (3.2.4)
41
- concurrent-ruby (1.2.3)
42
- connection_pool (2.4.1)
43
- crass (1.0.6)
44
- drb (2.2.0)
45
- ruby2_keywords
46
- erubi (1.12.0)
47
- i18n (1.14.1)
48
- concurrent-ruby (~> 1.0)
49
- io-console (0.7.2)
50
- irb (1.13.1)
51
- rdoc (>= 4.0.0)
52
- reline (>= 0.4.2)
53
- json (2.7.1)
54
- language_server-protocol (3.17.0.3)
55
- lint_roller (1.1.0)
56
- loofah (2.22.0)
57
- crass (~> 1.0.2)
58
- nokogiri (>= 1.12.0)
59
- minitest (5.22.2)
60
- mutex_m (0.2.0)
61
- nio4r (2.7.0)
62
- nokogiri (1.16.2)
63
- racc (~> 1.4)
64
- parallel (1.24.0)
65
- parser (3.3.0.5)
66
- ast (~> 2.4.1)
67
- racc
68
- psych (5.1.2)
69
- stringio
70
- puma (6.4.2)
71
- nio4r (~> 2.0)
72
- racc (1.7.3)
73
- rack (3.1.7)
74
- rack-session (2.0.0)
75
- rack (>= 3.0.0)
76
- rack-test (2.1.0)
77
- rack (>= 1.3)
78
- rackup (2.1.0)
79
- rack (>= 3)
80
- webrick (~> 1.8)
81
- rails-dom-testing (2.2.0)
82
- activesupport (>= 5.0.0)
83
- minitest
84
- nokogiri (>= 1.6)
85
- rails-html-sanitizer (1.6.0)
86
- loofah (~> 2.21)
87
- nokogiri (~> 1.14)
88
- railties (7.1.3)
89
- actionpack (= 7.1.3)
90
- activesupport (= 7.1.3)
91
- irb
92
- rackup (>= 1.0.0)
93
- rake (>= 12.2)
94
- thor (~> 1.0, >= 1.2.2)
95
- zeitwerk (~> 2.6)
96
- rainbow (3.1.1)
97
- rake (13.1.0)
98
- rdoc (6.6.3.1)
99
- psych (>= 4.0.0)
100
- regexp_parser (2.9.0)
101
- reline (0.5.7)
102
- io-console (~> 0.5)
103
- rexml (3.2.6)
104
- rubocop (1.59.0)
105
- json (~> 2.3)
106
- language_server-protocol (>= 3.17.0)
107
- parallel (~> 1.10)
108
- parser (>= 3.2.2.4)
109
- rainbow (>= 2.2.2, < 4.0)
110
- regexp_parser (>= 1.8, < 3.0)
111
- rexml (>= 3.2.5, < 4.0)
112
- rubocop-ast (>= 1.30.0, < 2.0)
113
- ruby-progressbar (~> 1.7)
114
- unicode-display_width (>= 2.4.0, < 3.0)
115
- rubocop-ast (1.30.0)
116
- parser (>= 3.2.1.0)
117
- rubocop-performance (1.20.2)
118
- rubocop (>= 1.48.1, < 2.0)
119
- rubocop-ast (>= 1.30.0, < 2.0)
120
- ruby-progressbar (1.13.0)
121
- ruby2_keywords (0.0.5)
122
- standard (1.33.0)
123
- language_server-protocol (~> 3.17.0.2)
124
- lint_roller (~> 1.0)
125
- rubocop (~> 1.59.0)
126
- standard-custom (~> 1.0.0)
127
- standard-performance (~> 1.3)
128
- standard-custom (1.0.2)
129
- lint_roller (~> 1.0)
130
- rubocop (~> 1.50)
131
- standard-performance (1.3.1)
132
- lint_roller (~> 1.1)
133
- rubocop-performance (~> 1.20.2)
134
- stringio (3.1.1)
135
- thor (1.3.0)
136
- tzinfo (2.0.6)
137
- concurrent-ruby (~> 1.0)
138
- unicode-display_width (2.5.0)
139
- webrick (1.8.1)
140
- zeitwerk (2.6.13)
141
-
142
- PLATFORMS
143
- ruby
144
-
145
- DEPENDENCIES
146
- bundler
147
- cypress-rails!
148
- minitest (~> 5.0)
149
- rake (~> 13.0)
150
- standard (>= 0.2.0)
151
-
152
- BUNDLED WITH
153
- 2.3.13
@@ -1,26 +0,0 @@
1
- require_relative "resets_state"
2
-
3
- module CypressRails
4
- class TracksResets
5
- def self.instance
6
- @instance ||= new
7
- end
8
-
9
- def reset_needed!
10
- @reset_needed = true
11
- end
12
-
13
- def reset_state_if_needed(transactional_server)
14
- if @reset_needed
15
- ResetsState.new.call(transactional_server: transactional_server)
16
- @reset_needed = false
17
- end
18
- end
19
-
20
- private
21
-
22
- def initialize
23
- @reset_needed = false
24
- end
25
- end
26
- end