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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -0
- data/Gemfile +12 -0
- data/README.md +52 -37
- data/Rakefile +2 -0
- data/lib/unreliable/build_order.rb +31 -7
- data/lib/unreliable/config.rb +4 -4
- data/lib/unreliable/version.rb +1 -1
- data/spec/adapter_option_spec.rb +7 -0
- data/spec/config_disable_nesting_spec.rb +24 -0
- data/spec/config_thread_safety_spec.rb +27 -0
- data/spec/env_spec.rb +2 -2
- data/spec/examples.txt +62 -39
- data/spec/execute_eager_load_spec.rb +109 -0
- data/spec/execute_find_each_spec.rb +49 -0
- data/spec/execute_pluck_exists_aggregate_spec.rb +48 -0
- data/spec/execute_queries_spec.rb +125 -0
- data/spec/execute_subqueries_spec.rb +46 -0
- data/spec/model_first_last_spec.rb +28 -0
- data/spec/model_group_spec.rb +35 -0
- data/spec/model_indexes_books_spec.rb +3 -3
- data/spec/model_indexes_cats_spec.rb +2 -2
- data/spec/model_indexes_dreams_spec.rb +2 -2
- data/spec/model_indexes_shelves_spec.rb +12 -6
- data/spec/model_internal_metadata_spec.rb +18 -0
- data/spec/model_joins_spec.rb +8 -8
- data/spec/model_limit_spec.rb +18 -0
- data/spec/model_or_spec.rb +31 -0
- data/spec/model_reorder_spec.rb +20 -0
- data/spec/model_select_distinct_spec.rb +48 -0
- data/spec/model_select_spec.rb +5 -5
- data/spec/model_subquery_spec.rb +7 -4
- data/spec/model_update_arel_10_spec.rb +84 -6
- data/spec/pluck_exists_aggregate_spec.rb +48 -0
- data/spec/spec_helper.rb +141 -1
- data/spec/textual_order_raw_spec.rb +100 -0
- data/spec/textual_order_spec.rb +14 -10
- data/spec/version_spec.rb +1 -1
- metadata +28 -112
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 17d0865e8524f14bc5d1ddc01106ccf63adb25f044a7ea3ca9109d44c6f31f3f
|
|
4
|
+
data.tar.gz: c3b5ff4213d9fb54bd3a48838ad0642c4a132e80715e45500ecf3c6884cd6f47
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
18
|
+
gem "unreliable", "~> 0.10"
|
|
19
19
|
end
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
95
|
+
`unreliable` is tested on every valid combination of:
|
|
83
96
|
|
|
84
|
-
|
|
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
|
|
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::
|
|
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
|
-
|
|
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
|
|
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'
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
138
|
+
standardrb
|
|
126
139
|
```
|
|
127
140
|
|
|
128
|
-
|
|
141
|
+
Run its tests in separate passes:
|
|
129
142
|
|
|
130
143
|
```
|
|
131
|
-
bundle exec
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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 ([
|
|
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.
|
|
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
|
@@ -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
|
|
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
|
|
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/
|
|
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
|
|
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
|
-
[
|
|
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
|
-
.
|
|
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)
|
data/lib/unreliable/config.rb
CHANGED
|
@@ -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
|
-
|
|
15
|
-
|
|
14
|
+
was_disabled = Thread.current[:unreliable_disabled]
|
|
15
|
+
Thread.current[:unreliable_disabled] = true
|
|
16
16
|
yield
|
|
17
17
|
ensure
|
|
18
|
-
|
|
18
|
+
Thread.current[:unreliable_disabled] = was_disabled
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
end
|
data/lib/unreliable/version.rb
CHANGED
|
@@ -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
|
|
2
|
-
------------------------------------------ |
|
|
3
|
-
./spec/
|
|
4
|
-
./spec/env_spec.rb[1:
|
|
5
|
-
./spec/
|
|
6
|
-
./spec/
|
|
7
|
-
./spec/
|
|
8
|
-
./spec/
|
|
9
|
-
./spec/
|
|
10
|
-
./spec/
|
|
11
|
-
./spec/
|
|
12
|
-
./spec/
|
|
13
|
-
./spec/
|
|
14
|
-
./spec/
|
|
15
|
-
./spec/
|
|
16
|
-
./spec/
|
|
17
|
-
./spec/
|
|
18
|
-
./spec/
|
|
19
|
-
./spec/
|
|
20
|
-
./spec/
|
|
21
|
-
./spec/
|
|
22
|
-
./spec/
|
|
23
|
-
./spec/
|
|
24
|
-
./spec/
|
|
25
|
-
./spec/
|
|
26
|
-
./spec/
|
|
27
|
-
./spec/
|
|
28
|
-
./spec/
|
|
29
|
-
./spec/
|
|
30
|
-
./spec/
|
|
31
|
-
./spec/
|
|
32
|
-
./spec/
|
|
33
|
-
./spec/
|
|
34
|
-
./spec/
|
|
35
|
-
./spec/
|
|
36
|
-
./spec/
|
|
37
|
-
./spec/
|
|
38
|
-
./spec/
|
|
39
|
-
./spec/
|
|
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 |
|