unreliable 0.9.1 → 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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +28 -0
  3. data/Gemfile +12 -0
  4. data/README.md +52 -37
  5. data/Rakefile +2 -0
  6. data/lib/unreliable/build_order.rb +31 -7
  7. data/lib/unreliable/config.rb +4 -4
  8. data/lib/unreliable/version.rb +1 -1
  9. data/spec/adapter_option_spec.rb +7 -0
  10. data/spec/config_disable_nesting_spec.rb +24 -0
  11. data/spec/config_thread_safety_spec.rb +27 -0
  12. data/spec/env_spec.rb +2 -2
  13. data/spec/examples.txt +62 -39
  14. data/spec/execute_eager_load_spec.rb +109 -0
  15. data/spec/execute_find_each_spec.rb +49 -0
  16. data/spec/execute_pluck_exists_aggregate_spec.rb +48 -0
  17. data/spec/execute_queries_spec.rb +125 -0
  18. data/spec/execute_subqueries_spec.rb +46 -0
  19. data/spec/model_first_last_spec.rb +28 -0
  20. data/spec/model_group_spec.rb +35 -0
  21. data/spec/model_indexes_books_spec.rb +3 -3
  22. data/spec/model_indexes_cats_spec.rb +2 -2
  23. data/spec/model_indexes_dreams_spec.rb +2 -2
  24. data/spec/model_indexes_shelves_spec.rb +12 -6
  25. data/spec/model_internal_metadata_spec.rb +18 -0
  26. data/spec/model_joins_spec.rb +8 -8
  27. data/spec/model_limit_spec.rb +18 -0
  28. data/spec/model_or_spec.rb +31 -0
  29. data/spec/model_reorder_spec.rb +20 -0
  30. data/spec/model_select_distinct_spec.rb +48 -0
  31. data/spec/model_select_spec.rb +5 -5
  32. data/spec/model_subquery_spec.rb +7 -4
  33. data/spec/model_update_arel_10_spec.rb +84 -6
  34. data/spec/pluck_exists_aggregate_spec.rb +48 -0
  35. data/spec/spec_helper.rb +141 -1
  36. data/spec/textual_order_raw_spec.rb +100 -0
  37. data/spec/textual_order_spec.rb +14 -10
  38. data/spec/version_spec.rb +1 -1
  39. metadata +28 -112
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9b11225a3c1dad62d2a283c3eb36fef7b69fda61815df13a860ce9c1970016eb
4
- data.tar.gz: 1994dd9ea21ba01dc839c56cc08ca91742465dc9b850c0b6a5eebbc81c7558a2
3
+ metadata.gz: 17d0865e8524f14bc5d1ddc01106ccf63adb25f044a7ea3ca9109d44c6f31f3f
4
+ data.tar.gz: c3b5ff4213d9fb54bd3a48838ad0642c4a132e80715e45500ecf3c6884cd6f47
5
5
  SHA512:
6
- metadata.gz: 145d5bbe8790fe4ee33bf4f0ea874350edf9655c2ebed35bfb3f18acb123d03efbe86c826dd0d9d4ca7ff12938c368e5cc4ebdc7afba224e0a8c12669a92e816
7
- data.tar.gz: 2a3ca2dfa53cbf67a0f8a6b7128df829874409a698586c15d10746624a71a18eddc77914768ec5da361541aa54706bd1899f473aa1f6093ec839f689428b36ed
6
+ metadata.gz: 461eb8a9445d48f09ff0b6e89f097fba52685f55e93637c1e2c6bc582104af44d3e30d8c503f10e145f1a3ef052f9fdaf2b4c9cb2544f3e71080f87ea2d9854a
7
+ data.tar.gz: 892d721b2ff0b45430625c1feae890ce0dbdc194dcab3118f8d20251af0eb0ee2c2d45bc4550bfd5ddbff334524c42c67b2d642a53709efa9c289e27a866f956
data/CHANGELOG.md CHANGED
@@ -1,3 +1,31 @@
1
+ ## Unreliable 1.0.0 (April 18, 2026) ##
2
+
3
+ ### Fixed
4
+
5
+ * Fixed crash on Rails 7.2+ caused by removal of `Arel::Table.engine` — replaced with `lease_connection` helper.
6
+ * `Config.disable` is now thread-safe — uses `Thread.current` instead of toggling a global flag, so parallel test runners don't interfere with each other.
7
+
8
+ ### Added
9
+
10
+ * SQL Server support — queries get `ORDER BY NEWID()`; DISTINCT queries are skipped (same rationale as PostgreSQL).
11
+ * Trilogy MySQL client support alongside `mysql2`.
12
+ * Rails 7.2, 8.0, and 8.1 compatibility; upper version bound raised to `< 9.0`.
13
+ * Ruby 3.4 and 4.0 added to the CI matrix.
14
+ * Many new specs: eager loading, `first`/`last`, `limit`, `reorder`, `group`/`having`, `or` queries, `pluck`/`exists?`/aggregates, `find_each`, and thread safety.
15
+
16
+ ### Changed
17
+
18
+ * Development dependencies moved from gemspec to Gemfile.
19
+ * Suppressed C-extension deprecation warnings from sqlite3 1.3.x on Ruby 2.7 (#4).
20
+
21
+ ## Unreliable 0.10.0 (January 15, 2024) ##
22
+
23
+ ### Changed
24
+
25
+ * Rails 5.0 and 5.1 compatibility removed due to bug in interaction with Arel 8. This is not a breaking change because it didn't work before. If you must run Rails < 5.2, do not use Unreliable.
26
+ * PostgreSQL bug with SELECT DISTINCT fixed (#10).
27
+ * Many tests added; CI dockerized; test suite runs against SQLite, PostgreSQL, and MySQL (#3).
28
+
1
29
  ## Unreliable 0.9.1 (November 21, 2022) ##
2
30
 
3
31
  ### Changed
data/Gemfile CHANGED
@@ -3,3 +3,15 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gemspec
6
+
7
+ gem "appraisal", "~> 2.4"
8
+ gem "bundler", "~> 2.1"
9
+ gem "combustion", "~> 1.5"
10
+ gem "mysql2", "~> 0.5"
11
+ gem "ostruct"
12
+ gem "pg", "~> 1.5"
13
+ gem "rake", "~> 13.0"
14
+ gem "rspec", "~> 3.0"
15
+ gem "sqlite3", (RUBY_VERSION >= "3.2") ? "~> 1.6.9" : "~> 1.5.4"
16
+ gem "standard", "~> 1.17"
17
+ gem "yamllint", "~> 0.0.9"
data/README.md CHANGED
@@ -15,11 +15,11 @@ Add `unreliable` to your `Gemfile`'s `test` group:
15
15
  # Gemfile
16
16
 
17
17
  group :test do
18
- gem "unreliable", "~> 0.1"
18
+ gem "unreliable", "~> 0.10"
19
19
  end
20
20
  ```
21
21
 
22
- The next time your test suite runs, it may emit new errors and failures. If so, great!
22
+ And run `bundle install`. Then try running your test suite. If it emits new errors and failures, great!
23
23
 
24
24
  ## The problem with orders
25
25
 
@@ -27,17 +27,26 @@ Here's an [open secret](#references): **relational databases do not guarantee th
27
27
 
28
28
  If all your ActiveRecord ordering is already unambiguous, congratulations! `unreliable` will have no effect.
29
29
 
30
- But sometimes we think we specified an unambiguous order, but didn't. Maybe we ordered on timestamps, which are usually unique but sometimes not. And the test suite will stay silent as long as our database just happens to return the same order.
30
+ But sometimes... we think we specified an unambiguous order, but we didn't. For example, maybe we ordered on timestamps, which are usually unique but sometimes not.
31
31
 
32
- If ambiguous ordering is fine for your app's purposes, but your tests rely on a specific order, that's a bug in your tests. Your tests are incorrectly failing -- rarely -- which can be confusing and annoying.
32
+ The test suite will stay silent about that, as long as our database just happens to return the same order. That silence is a problem.
33
33
 
34
- Or, if your Rails code relies on that accidental ordering, that's a bug in your app. Your tests are passing when they should be failing.
34
+ If ambiguous ordering is fine for your app's purposes, but your tests rely on a specific order, that's a **bug in your tests**. Your tests are incorrectly failing -- rarely -- which can be confusing and annoying.
35
+
36
+ Or, if your Rails code relies on that accidental ordering, that's a **bug in your app**. Your tests are passing when they should be failing.
35
37
 
36
38
  In both cases, `unreliable` exposes the problem by making those tests fail most of the time.
37
39
 
38
40
  ## Fixing the new failures
39
41
 
40
- When `unreliable` turns up a new test failure, you fix it in one of two ways. Either relax your test so it stops relying on order, or tighten up your app to specify order rigorously. (In my company's app, it was about 50/50.)
42
+ When `unreliable` turns up a new test failure, you fix it in one of two ways.
43
+
44
+ Either:
45
+
46
+ * relax your test so it stops relying on order,
47
+ * or tighten up your app to specify order rigorously.
48
+
49
+ In my company's app, it was about 50/50.
41
50
 
42
51
  ### Relax a test
43
52
 
@@ -45,7 +54,7 @@ Take a look at what your test is checking. If you're testing a method or an endp
45
54
 
46
55
  * Make your test accept all correct answers. For example, sort an array in the method's response before comparing.
47
56
 
48
- * Help your test suite focus on what you're testing. If your fixtures' "latest" element could change because they don't specify a timestamp, that might be a distraction that's not relevant to how your app works, so you could assign timestamps to the fixtures.
57
+ * Help your test suite focus on what you're testing. If your fixtures' "latest" element could change because they don't specify a timestamp, that might be a distraction that's not relevant to how your app works, so you could assign unique timestamps to the fixtures.
49
58
 
50
59
  This makes your test suite more robust.
51
60
 
@@ -53,7 +62,9 @@ If your test suite is checking generated `.to_sql` against known-good SQL text,
53
62
 
54
63
  ### Tighten the app
55
64
 
56
- If your app should be returning results in a particular order, and now with `unreliable` it sometimes does not, your test is correct and your app is wrong. Specify order rigorously in your app.
65
+ If your app should be returning results in a particular order, and now with `unreliable` it sometimes does not, your test is correct and your app is wrong.
66
+
67
+ Specify order rigorously in your app.
57
68
 
58
69
  Maybe you're testing `Book.reverse_chron.first`, and you've defined that ordering this way:
59
70
 
@@ -75,19 +86,25 @@ Or, if `title` is not unique:
75
86
  scope :reverse_chron, -> { order(year_published: :desc, title: :desc, id: :desc) }
76
87
  ```
77
88
 
78
- The problem in this example is easy to see because many books are published each year. But this error can occur at any time granularity.
89
+ This example's problem is easy to see because many books are published each year.
90
+
91
+ But this error can occur at any granularity, in time or other data types.
79
92
 
80
93
  ## Requirements
81
94
 
82
- `unreliable` is tested to support Ruby 2.6 through 3.1, and Rails 5.0 through 7.0.
95
+ `unreliable` is tested on every valid combination of:
83
96
 
84
- As of November 2022, this is all released versions of both that are currently supported, plus several older releases.
97
+ * sqlite, postgresql, mysql2, trilogy, and sqlserver adapters
98
+ * Ruby 2.6 through 4.0
99
+ * Rails 5.2 through 8.1
100
+
101
+ SQL Server tests run in CI (x86\_64) only; the SQL Server 2022 Docker image has no arm64 variant and is unstable under QEMU on Apple Silicon.
85
102
 
86
103
  `unreliable` depends only on ActiveRecord and Railties. If you have a non-Rails app that uses ActiveRecord, you can still use it.
87
104
 
88
105
  ## Implementation
89
106
 
90
- `unreliable` does exactly nothing outside of test environments. There is intentionally no way to enable `unreliable` in production, and there never will be.
107
+ `unreliable` does exactly nothing outside of test environments. There is intentionally no way to enable it in production, and there never will be.
91
108
 
92
109
  In a Rails test environment, `unreliable` patches ActiveRecord to append a final `ORDER BY` clause, when necessary, that returns results in a random order.
93
110
 
@@ -95,53 +112,51 @@ Because it's appended, the existing ordering is not affected unless it is ambigu
95
112
 
96
113
  With `unreliable` installed, every ActiveRecord relation invoked by the test suite will have any ambiguity replaced with randomness. Tests that rely on the ordering of two records will break half the time. Tests with three or more break most of the time.
97
114
 
98
- `unreliable` patches `ActiveRecord::QueryMethods#build_arel`, the point where an Arel is converted for use, to append an order to the existing order chain. (The patch is applied after ActiveRecord loads, using `ActiveSupport.on_load`, the standard interface since Rails 4.0.) It works with MySQL, Postgres, and SQLite.
115
+ `unreliable` patches `ActiveRecord::Relation#build_order`, the point where ordering is assembled, to append a random-order term to the existing order chain. (The patch is applied after ActiveRecord loads, using `ActiveSupport.on_load`, the standard interface since Rails 4.0.) It works with MySQL, PostgreSQL, SQLite, and SQL Server.
116
+
117
+ Patching `build_order` ensures that the `ORDER BY` applies to not just `SELECT` but e.g. `delete_all` and `update_all`. It also applies within subqueries.
99
118
 
100
- This means that the `ORDER BY` applies to not just `SELECT` but e.g. `delete_all` and `update_all`. It also applies within subqueries.
119
+ The patch is only applied when `Rails.env.test?`, so it has zero effect on production. Just to be sure, the environment is also checked on every invocation, to make absolutely certain this code can run only in `test`.
101
120
 
102
- The patch is only applied when `Rails.env.test?`, and that boolean is also checked on every invocation, just to make certain it has no effect in any other environment.
121
+ The gem has a large test suite that checks for correctness at several abstraction layers inside ActiveRecord. It ensures the correct SQL is generated and that it executes correctly.
103
122
 
104
123
  ### No dual-purpose environment please
105
124
 
106
- Your test environment is just for running your test suite. If you've overloaded the test environment to do any actual work, you'd be frustrated when `unreliable` slows it down and changes its behavior, so don't install it (yet).
125
+ Your test environment is just for running your test suite. If you've overloaded the test environment to do any actual work, you'll be frustrated when `unreliable` slows it down and changes its behavior. Don't do that.
107
126
 
108
127
  ## Contributing
109
128
 
110
- Thoughts and suggestions are welcome. Please read the code of conduct, then create an issue or pull request on GitHub. If you just have questions, go ahead and open an issue, I'm pretty friendly.
129
+ Thoughts and suggestions are welcome. Please read the code of conduct, then create an issue or pull request on GitHub. If you just have questions, please go ahead and open an issue!
111
130
 
112
131
  ### Run the gem's tests
113
132
 
114
- To test locally, against the different versions of ActiveRecord, use Ruby 2.7, the only version currently compatible with all the ActiveRecord versions supported. Install the required gems with:
115
-
116
- ```
117
- gem install bundler
118
- bundle install
119
- bundle exec appraisal install
120
- ```
133
+ To test locally, see the hint at the top of `compose.yaml` to spin up docker containers.
121
134
 
122
- Run `unreliable`'s linter with:
135
+ After you spin up the containers and open a shell in the app container, run `unreliable`'s linter with:
123
136
 
124
137
  ```
125
- bundle exec standardrb
138
+ standardrb
126
139
  ```
127
140
 
128
- Then you can run `unreliable`'s tests with:
141
+ Run its tests in separate passes:
129
142
 
130
143
  ```
131
- bundle exec appraisal rake
144
+ RSPEC_ADAPTER=sqlite bundle exec rake
145
+ RSPEC_ADAPTER=postgresql bundle exec rake
146
+ RSPEC_ADAPTER=mysql2 bundle exec rake
147
+ RSPEC_ADAPTER=trilogy bundle exec rake
148
+ RSPEC_ADAPTER=sqlserver bundle exec rake
132
149
  ```
133
150
 
134
- Appraisal ensures the tests run against every compatible minor version of ActiveRecord.
135
-
136
- The GitHub CI workflow in `.github/` ensures those tests are also run against against every compatible minor version of Ruby. Your PR won't trigger my GitHub project's workflow, but you're welcome to run your own, or ask me to run mine manually.
151
+ The SQL Server pass requires an x86\_64 host; the SQL Server 2022 Docker image does not run on Apple Silicon.
137
152
 
138
- Testing against ActiveRecord is done with [Combustion](https://github.com/pat/combustion), which stands up a local SQLite database and ActiveRecord-based models for it. This gives more reliable coverage than mocking unit tests within ActiveRecord itself, though I do some of that too.
153
+ The GitHub CI workflow in `.github/` ensures those tests are also run against every compatible minor version of Ruby. Your PR won't trigger my GitHub project's workflow, but you're welcome to run your own, or ask me to run mine manually.
139
154
 
140
155
  ### Experiment
141
156
 
142
157
  If you'd like to see `unreliable` in action on a small but real Rails app locally, you can do this:
143
158
 
144
- 1. In a directory next to your `unreliable` working directory, create a `.ruby-version` of `2.7.6` and a 2-line `Gemfile`: `source "https://rubygems.org"`, `gem "rails", "~> 7.0"`
159
+ 1. In a directory next to your `unreliable` working directory, create a `.ruby-version` of `2.7.8` and a 2-line `Gemfile`: `source "https://rubygems.org"`, `gem "rails", "~> 7.0"`
145
160
  2. `bundle install && bundle exec rails new . --force`
146
161
  3. `echo 'gem "unreliable", path: "../unreliable"' >> Gemfile`
147
162
  4. `bundle install && bundle exec rails generate model post title:string body:text`
@@ -166,8 +181,8 @@ The most common ambiguous ordering is an ORDER BY one column that is not unique,
166
181
  But there are other ways you can order a relation but still have your query be ambiguous:
167
182
 
168
183
  * ORDER BY multiple columns, but with no subset which is unique
169
- * ORDER BY a column with values that differ only by [character case](https://dev.mysql.com/doc/refman/8.0/en/sorting-rows.html)
170
- * ORDER BY values that are identical within the [prefix length limit](https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_max_sort_length) examined for sorting
184
+ * ORDER BY a column your [pre-Rails-6.1](https://guides.rubyonrails.org/6_1_release_notes.html#active-record-notable-changes) application thought was unique, but currently isn't, due to your non-UNIQUE database column's accent- or case-insensitive [collation](https://dev.mysql.com/doc/refman/8.0/en/charset-general.html)
185
+ * ORDER BY values that are identical only within the [prefix length limit](https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_max_sort_length) examined for sorting
171
186
 
172
187
  `unreliable` ensures correct testing because it appends a random order to each of these cases.
173
188
 
@@ -181,11 +196,11 @@ MySQL ([5.6](https://dev.mysql.com/doc/refman/5.6/en/limit-optimization.html), [
181
196
 
182
197
  > If multiple rows have identical values in the `ORDER BY` columns, the server is free to return those rows in any order, and may do so differently depending on the overall execution plan. In other words, the sort order of those rows is nondeterministic with respect to the nonordered columns.
183
198
 
184
- Postgres ([12](https://www.postgresql.org/docs/12/sql-select.html#SQL-ORDERBY), [13](https://www.postgresql.org/docs/13/sql-select.html#SQL-ORDERBY), [14](https://www.postgresql.org/docs/14/sql-select.html#SQL-ORDERBY)):
199
+ Postgres ([13](https://www.postgresql.org/docs/13/sql-select.html#SQL-ORDERBY), [14](https://www.postgresql.org/docs/14/sql-select.html#SQL-ORDERBY), [15](https://www.postgresql.org/docs/15/sql-select.html#SQL-ORDERBY), [16](https://www.postgresql.org/docs/16/sql-select.html#SQL-ORDERBY)):
185
200
 
186
201
  > If two rows are equal according to the leftmost expression, they are compared according to the next expression and so on. If they are equal according to all specified expressions, they are returned in an implementation-dependent order.
187
202
 
188
- SQLite ([3.39](https://www.sqlite.org/lang_select.html#the_order_by_clause)):
203
+ SQLite ([3.45](https://www.sqlite.org/lang_select.html#the_order_by_clause)):
189
204
 
190
205
  > The order in which two rows for which all ORDER BY expressions evaluate to equal values are returned is undefined.
191
206
 
data/Rakefile CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "bundler/gem_tasks"
4
+
3
5
  begin
4
6
  require "rspec/core/rake_task"
5
7
 
@@ -7,28 +7,45 @@ require "active_record/connection_adapters/abstract_adapter"
7
7
  module Unreliable
8
8
  module BuildOrder
9
9
  def build_order(arel)
10
- super(arel)
10
+ super
11
11
 
12
+ adapter_name = unreliable_connection.adapter_name
12
13
  return unless Unreliable::Config.enabled?
14
+ return if distinct_on_postgres?(adapter_name)
15
+ return if distinct_on_sqlserver?(adapter_name)
13
16
  return if from_only_internal_metadata?(arel)
14
17
  return if from_one_table_with_ordered_pk?(arel)
15
18
 
16
- case Arel::Table.engine.connection.adapter_name
17
- when "Mysql2"
19
+ case adapter_name
20
+ when "Mysql2", "Trilogy"
18
21
  # https://dev.mysql.com/doc/refman/8.0/en/mathematical-functions.html#function_rand
19
22
  arel.order("RAND()")
20
23
 
24
+ when "SQLServer"
25
+ # https://learn.microsoft.com/en-us/sql/t-sql/functions/newid-transact-sql
26
+ arel.order("NEWID()")
27
+
21
28
  when "PostgreSQL", "SQLite"
22
- # https://www.postgresql.org/docs/13/functions-math.html#FUNCTIONS-MATH-RANDOM-TABLE
29
+ # https://www.postgresql.org/docs/16/functions-math.html#FUNCTIONS-MATH-RANDOM-TABLE
23
30
  # https://www.sqlite.org/lang_corefunc.html#random
24
31
  arel.order("RANDOM()")
25
32
 
26
33
  else
27
- raise ArgumentError, "unknown Arel::Table.engine"
34
+ raise ArgumentError, "unreliable: unknown adapter #{adapter_name.inspect}"
28
35
 
29
36
  end
30
37
  end
31
38
 
39
+ def distinct_on_postgres?(adapter_name)
40
+ distinct_value && adapter_name == "PostgreSQL"
41
+ end
42
+
43
+ def distinct_on_sqlserver?(adapter_name)
44
+ # SQL Server rejects ORDER BY expressions not in the select list when
45
+ # DISTINCT is used, so we don't append NEWID() to DISTINCT queries.
46
+ distinct_value && adapter_name == "SQLServer"
47
+ end
48
+
32
49
  def from_only_internal_metadata?(arel)
33
50
  # No need to randomize queries on ar_internal_metadata
34
51
  arel.froms.map(&:name) == [ActiveRecord::Base.internal_metadata_table_name]
@@ -51,13 +68,20 @@ module Unreliable
51
68
  # Using the SchemaCache minimizes the number of times we have to, e.g. in MySQL,
52
69
  # SELECT column_name FROM information_schema.statistics
53
70
  # (or in Rails < 6, SELECT column_name FROM information_schema.key_column_usage)
54
- [ActiveRecord::Base.connection.schema_cache.primary_keys(arel.froms.first.name)].flatten
71
+ [unreliable_connection.schema_cache.primary_keys(arel.froms.first.name)].flatten
72
+ end
73
+
74
+ def unreliable_connection
75
+ # Rails 7.2+ soft-deprecated `connection` in favor of `lease_connection`.
76
+ # We check for it with klass (not self) because it works on Rails 7.2+ due to
77
+ # delegation, and it doesn't break Rails 5.2 due to a respond_to_missing bug.
78
+ klass.respond_to?(:lease_connection) ? lease_connection : connection
55
79
  end
56
80
 
57
81
  def order_columns(arel)
58
82
  from_table_name = arel.froms.first.name
59
83
  arel.orders
60
- .select { |order| order.is_a? Arel::Nodes::Ordering } # Don't try to parse textual orders
84
+ .grep(Arel::Nodes::Ordering) # Don't try to parse textual orders
61
85
  .map(&:expr)
62
86
  .select { |expr| expr.relation.name == from_table_name }
63
87
  .map(&:name)
@@ -7,15 +7,15 @@ module Unreliable
7
7
  end
8
8
 
9
9
  def self.enabled?
10
- @enabled && Rails.env.test?
10
+ @enabled && !Thread.current[:unreliable_disabled] && Rails.env.test?
11
11
  end
12
12
 
13
13
  def self.disable
14
- prev_enabled = @enabled
15
- @enabled = false
14
+ was_disabled = Thread.current[:unreliable_disabled]
15
+ Thread.current[:unreliable_disabled] = true
16
16
  yield
17
17
  ensure
18
- @enabled = prev_enabled
18
+ Thread.current[:unreliable_disabled] = was_disabled
19
19
  end
20
20
  end
21
21
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Unreliable
4
- VERSION = "0.9.1"
4
+ VERSION = "1.0.0"
5
5
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "rspec adapter" do
4
+ it "is as specified" do
5
+ expect(ActiveRecord::Base.connection.adapter_name.downcase).to eq(UnreliableTest.find_adapter)
6
+ end
7
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Unreliable::Config do
4
+ it "restores state after nested disable blocks" do
5
+ expect(Cat.all.to_sql).to end_with(adapter_rand("ORDER BY RANDOM()"))
6
+
7
+ Unreliable::Config.disable do
8
+ expect(Cat.all.to_sql).to_not include("RANDOM()")
9
+ expect(Cat.all.to_sql).to_not include("RAND()")
10
+
11
+ Unreliable::Config.disable do
12
+ expect(Cat.all.to_sql).to_not include("RANDOM()")
13
+ expect(Cat.all.to_sql).to_not include("RAND()")
14
+ end
15
+
16
+ # Still disabled after inner block
17
+ expect(Cat.all.to_sql).to_not include("RANDOM()")
18
+ expect(Cat.all.to_sql).to_not include("RAND()")
19
+ end
20
+
21
+ # Re-enabled after outer block
22
+ expect(Cat.all.to_sql).to end_with(adapter_rand("ORDER BY RANDOM()"))
23
+ end
24
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Config.disable must only suppress randomization in the calling thread.
4
+ # Before the fix, @enabled was a class-level variable shared across threads,
5
+ # so one thread's disable block would suppress randomization in all threads.
6
+
7
+ RSpec.describe Unreliable::Config, "thread safety" do
8
+ it "does not affect other threads when disabled" do
9
+ other_thread_sql = nil
10
+ barrier = Queue.new
11
+
12
+ Unreliable::Config.disable do
13
+ # Our thread has randomization disabled
14
+ expect(Cat.all.to_sql).to_not include("RANDOM()")
15
+ expect(Cat.all.to_sql).to_not include("RAND()")
16
+
17
+ Thread.new do
18
+ other_thread_sql = Cat.all.to_sql
19
+ barrier.push(:done)
20
+ end
21
+
22
+ barrier.pop # wait for the other thread
23
+ end
24
+
25
+ expect(other_thread_sql).to include(adapter_rand("RANDOM()"))
26
+ end
27
+ end
data/spec/env_spec.rb CHANGED
@@ -3,14 +3,14 @@
3
3
  RSpec.describe Unreliable do
4
4
  it "does nothing in prod" do
5
5
  Rails.env = "production"
6
- expect(Cat.where(word: "foo").to_sql).to end_with(%q(WHERE "cats"."word" = 'foo'))
6
+ expect(Cat.where(word: "foo").to_sql).to end_with(adapter_rand(%q(WHERE "cats"."word" = 'foo')))
7
7
  ensure
8
8
  Rails.env = "test"
9
9
  end
10
10
 
11
11
  it "does nothing in dev" do
12
12
  Rails.env = "development"
13
- expect(Cat.where(word: "foo").to_sql).to end_with(%q(WHERE "cats"."word" = 'foo'))
13
+ expect(Cat.where(word: "foo").to_sql).to end_with(adapter_rand(%q(WHERE "cats"."word" = 'foo')))
14
14
  ensure
15
15
  Rails.env = "test"
16
16
  end
data/spec/examples.txt CHANGED
@@ -1,39 +1,62 @@
1
- example_id | status | run_time |
2
- ------------------------------------------ | ------ | --------------- |
3
- ./spec/env_spec.rb[1:1] | passed | 0.00346 seconds |
4
- ./spec/env_spec.rb[1:2] | passed | 0.00049 seconds |
5
- ./spec/model_cache_versioning_spec.rb[1:1] | passed | 0.03983 seconds |
6
- ./spec/model_indexes_books_spec.rb[1:1] | passed | 0.00152 seconds |
7
- ./spec/model_indexes_books_spec.rb[1:2] | passed | 0.00132 seconds |
8
- ./spec/model_indexes_books_spec.rb[1:3] | passed | 0.00108 seconds |
9
- ./spec/model_indexes_cats_spec.rb[1:1] | passed | 0.00105 seconds |
10
- ./spec/model_indexes_cats_spec.rb[1:2] | passed | 0.0015 seconds |
11
- ./spec/model_indexes_dreams_spec.rb[1:1] | passed | 0.00313 seconds |
12
- ./spec/model_indexes_dreams_spec.rb[1:2] | passed | 0.00129 seconds |
13
- ./spec/model_indexes_shelves_spec.rb[1:1] | passed | 0.00178 seconds |
14
- ./spec/model_indexes_shelves_spec.rb[1:2] | passed | 0.00174 seconds |
15
- ./spec/model_indexes_shelves_spec.rb[1:3] | passed | 0.00076 seconds |
16
- ./spec/model_indexes_shelves_spec.rb[1:4] | passed | 0.00087 seconds |
17
- ./spec/model_indexes_shelves_spec.rb[1:5] | passed | 0.00079 seconds |
18
- ./spec/model_indexes_shelves_spec.rb[1:6] | passed | 0.00101 seconds |
19
- ./spec/model_joins_spec.rb[1:1] | passed | 0.01262 seconds |
20
- ./spec/model_joins_spec.rb[1:2] | passed | 0.00359 seconds |
21
- ./spec/model_joins_spec.rb[1:3] | passed | 0.01319 seconds |
22
- ./spec/model_joins_spec.rb[1:4] | passed | 0.00217 seconds |
23
- ./spec/model_select_spec.rb[1:1] | passed | 0.0007 seconds |
24
- ./spec/model_select_spec.rb[1:2] | passed | 0.00093 seconds |
25
- ./spec/model_select_spec.rb[1:3] | passed | 0.00091 seconds |
26
- ./spec/model_select_spec.rb[1:4] | passed | 0.00127 seconds |
27
- ./spec/model_subquery_spec.rb[1:1] | passed | 0.00155 seconds |
28
- ./spec/model_update_arel_10_spec.rb[1:1] | passed | 0.00536 seconds |
29
- ./spec/railtie_spec.rb[1:1] | passed | 0.00013 seconds |
30
- ./spec/railtie_spec.rb[1:2] | passed | 0.00008 seconds |
31
- ./spec/textual_order_spec.rb[1:1] | passed | 0.00108 seconds |
32
- ./spec/textual_order_spec.rb[1:2] | passed | 0.00067 seconds |
33
- ./spec/textual_order_spec.rb[1:3] | passed | 0.00078 seconds |
34
- ./spec/textual_order_spec.rb[1:4] | passed | 0.00087 seconds |
35
- ./spec/textual_order_spec.rb[1:5] | passed | 0.00069 seconds |
36
- ./spec/textual_order_spec.rb[1:6] | passed | 0.00079 seconds |
37
- ./spec/textual_order_spec.rb[1:7] | passed | 0.00088 seconds |
38
- ./spec/version_spec.rb[1:1] | passed | 0.00151 seconds |
39
- ./spec/version_spec.rb[1:2] | passed | 0.0002 seconds |
1
+ example_id | status | run_time |
2
+ ------------------------------------------ | ------- | --------------- |
3
+ ./spec/adapter_option_spec.rb[1:1] | passed | 0.00045 seconds |
4
+ ./spec/env_spec.rb[1:1] | passed | 0.00516 seconds |
5
+ ./spec/env_spec.rb[1:2] | passed | 0.00012 seconds |
6
+ ./spec/execute_queries_spec.rb[1:1] | passed | 0.00479 seconds |
7
+ ./spec/execute_queries_spec.rb[1:2] | passed | 0.00774 seconds |
8
+ ./spec/execute_queries_spec.rb[1:3] | passed | 0.00722 seconds |
9
+ ./spec/execute_queries_spec.rb[1:4] | passed | 0.00989 seconds |
10
+ ./spec/execute_queries_spec.rb[1:5] | passed | 0.01507 seconds |
11
+ ./spec/execute_queries_spec.rb[1:6] | passed | 0.006 seconds |
12
+ ./spec/execute_queries_spec.rb[1:7] | passed | 0.00368 seconds |
13
+ ./spec/execute_queries_spec.rb[1:8] | passed | 0.00372 seconds |
14
+ ./spec/execute_queries_spec.rb[1:9] | passed | 0.00329 seconds |
15
+ ./spec/execute_subqueries_spec.rb[1:1] | passed | 0.00974 seconds |
16
+ ./spec/execute_subqueries_spec.rb[1:2] | passed | 0.00419 seconds |
17
+ ./spec/model_cache_versioning_spec.rb[1:1] | passed | 0.00092 seconds |
18
+ ./spec/model_indexes_books_spec.rb[1:1] | passed | 0.00058 seconds |
19
+ ./spec/model_indexes_books_spec.rb[1:2] | passed | 0.0002 seconds |
20
+ ./spec/model_indexes_books_spec.rb[1:3] | passed | 0.00007 seconds |
21
+ ./spec/model_indexes_cats_spec.rb[1:1] | passed | 0.00006 seconds |
22
+ ./spec/model_indexes_cats_spec.rb[1:2] | passed | 0.00009 seconds |
23
+ ./spec/model_indexes_dreams_spec.rb[1:1] | passed | 0.00008 seconds |
24
+ ./spec/model_indexes_dreams_spec.rb[1:2] | passed | 0.00007 seconds |
25
+ ./spec/model_indexes_shelves_spec.rb[1:1] | passed | 0.00053 seconds |
26
+ ./spec/model_indexes_shelves_spec.rb[1:2] | passed | 0.00022 seconds |
27
+ ./spec/model_indexes_shelves_spec.rb[1:3] | passed | 0.00008 seconds |
28
+ ./spec/model_indexes_shelves_spec.rb[1:4] | passed | 0.00006 seconds |
29
+ ./spec/model_indexes_shelves_spec.rb[1:5] | passed | 0.00007 seconds |
30
+ ./spec/model_indexes_shelves_spec.rb[1:6] | passed | 0.0001 seconds |
31
+ ./spec/model_joins_spec.rb[1:1] | passed | 0.00405 seconds |
32
+ ./spec/model_joins_spec.rb[1:2] | passed | 0.0006 seconds |
33
+ ./spec/model_joins_spec.rb[1:3] | passed | 0.00015 seconds |
34
+ ./spec/model_joins_spec.rb[1:4] | passed | 0.00024 seconds |
35
+ ./spec/model_select_distinct_spec.rb[1:1] | passed | 0.0001 seconds |
36
+ ./spec/model_select_distinct_spec.rb[1:2] | passed | 0.00009 seconds |
37
+ ./spec/model_select_distinct_spec.rb[1:3] | passed | 0.00007 seconds |
38
+ ./spec/model_select_distinct_spec.rb[1:4] | passed | 0.00123 seconds |
39
+ ./spec/model_select_spec.rb[1:1] | passed | 0.00007 seconds |
40
+ ./spec/model_select_spec.rb[1:2] | passed | 0.00008 seconds |
41
+ ./spec/model_select_spec.rb[1:3] | passed | 0.00007 seconds |
42
+ ./spec/model_select_spec.rb[1:4] | passed | 0.00013 seconds |
43
+ ./spec/model_subquery_spec.rb[1:1] | passed | 0.00014 seconds |
44
+ ./spec/model_update_arel_10_spec.rb[1:1] | passed | 0.00123 seconds |
45
+ ./spec/railtie_spec.rb[1:1] | passed | 0.00003 seconds |
46
+ ./spec/railtie_spec.rb[1:2] | passed | 0.00003 seconds |
47
+ ./spec/textual_order_raw_spec.rb[1:1] | passed | 0.00009 seconds |
48
+ ./spec/textual_order_raw_spec.rb[1:2] | passed | 0.00007 seconds |
49
+ ./spec/textual_order_raw_spec.rb[1:3] | pending | 0.00001 seconds |
50
+ ./spec/textual_order_raw_spec.rb[1:4] | pending | 0 seconds |
51
+ ./spec/textual_order_raw_spec.rb[1:5] | passed | 0.00007 seconds |
52
+ ./spec/textual_order_raw_spec.rb[1:6] | passed | 0.00006 seconds |
53
+ ./spec/textual_order_raw_spec.rb[1:7] | passed | 0.00006 seconds |
54
+ ./spec/textual_order_raw_spec.rb[1:8] | passed | 0.00006 seconds |
55
+ ./spec/textual_order_spec.rb[1:1] | passed | 0.00006 seconds |
56
+ ./spec/textual_order_spec.rb[1:2] | passed | 0.00006 seconds |
57
+ ./spec/textual_order_spec.rb[1:3] | passed | 0.00006 seconds |
58
+ ./spec/textual_order_spec.rb[1:4] | passed | 0.00006 seconds |
59
+ ./spec/textual_order_spec.rb[1:5] | passed | 0.00006 seconds |
60
+ ./spec/textual_order_spec.rb[1:6] | passed | 0.00007 seconds |
61
+ ./spec/version_spec.rb[1:1] | passed | 0.00054 seconds |
62
+ ./spec/version_spec.rb[1:2] | passed | 0.00004 seconds |