mongoid 7.2.2 → 7.2.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/README.md +1 -1
  4. data/lib/config/locales/en.yml +13 -0
  5. data/lib/mongoid/association/embedded/embeds_many/proxy.rb +1 -1
  6. data/lib/mongoid/association/proxy.rb +1 -1
  7. data/lib/mongoid/association/referenced/has_many/enumerable.rb +1 -1
  8. data/lib/mongoid/association/referenced/has_many/proxy.rb +1 -1
  9. data/lib/mongoid/association/relatable.rb +2 -0
  10. data/lib/mongoid/config/environment.rb +9 -1
  11. data/lib/mongoid/contextual/atomic.rb +7 -2
  12. data/lib/mongoid/contextual/none.rb +3 -0
  13. data/lib/mongoid/criteria/queryable/selectable.rb +2 -2
  14. data/lib/mongoid/criteria/queryable/storable.rb +4 -4
  15. data/lib/mongoid/criteria.rb +1 -1
  16. data/lib/mongoid/document.rb +3 -2
  17. data/lib/mongoid/errors/empty_config_file.rb +26 -0
  18. data/lib/mongoid/errors/invalid_config_file.rb +26 -0
  19. data/lib/mongoid/errors/mongoid_error.rb +1 -1
  20. data/lib/mongoid/errors.rb +2 -0
  21. data/lib/mongoid/interceptable.rb +1 -1
  22. data/lib/mongoid/persistence_context.rb +3 -1
  23. data/lib/mongoid/query_cache.rb +11 -1
  24. data/lib/mongoid/tasks/database.rb +1 -1
  25. data/lib/mongoid/validatable/associated.rb +1 -1
  26. data/lib/mongoid/validatable/presence.rb +3 -3
  27. data/lib/mongoid/validatable/uniqueness.rb +1 -1
  28. data/lib/mongoid/version.rb +1 -1
  29. data/lib/mongoid.rb +1 -0
  30. data/lib/rails/generators/mongoid/config/templates/mongoid.yml +1 -1
  31. data/spec/integration/app_spec.rb +3 -0
  32. data/spec/integration/contextual/empty_spec.rb +142 -0
  33. data/spec/integration/stringified_symbol_field_spec.rb +2 -2
  34. data/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb +17 -4
  35. data/spec/mongoid/association/referenced/belongs_to/proxy_spec.rb +17 -0
  36. data/spec/mongoid/association/referenced/belongs_to_query_spec.rb +20 -0
  37. data/spec/mongoid/association/referenced/has_many_models.rb +17 -0
  38. data/spec/mongoid/clients/factory_spec.rb +9 -3
  39. data/spec/mongoid/clients/options_spec.rb +11 -3
  40. data/spec/mongoid/config/environment_spec.rb +86 -8
  41. data/spec/mongoid/contextual/atomic_spec.rb +64 -25
  42. data/spec/mongoid/contextual/geo_near_spec.rb +1 -1
  43. data/spec/mongoid/criteria_spec.rb +4 -0
  44. data/spec/mongoid/document_fields_spec.rb +26 -0
  45. data/spec/mongoid/document_query_spec.rb +51 -0
  46. data/spec/mongoid/document_spec.rb +21 -1
  47. data/spec/mongoid/errors/invalid_config_file_spec.rb +32 -0
  48. data/spec/mongoid/errors/mongoid_error_spec.rb +20 -8
  49. data/spec/mongoid/persistable/updatable_spec.rb +2 -0
  50. data/spec/mongoid/persistable_spec.rb +2 -2
  51. data/spec/mongoid/query_cache_spec.rb +24 -0
  52. data/spec/shared/bin/s3-copy +45 -0
  53. data/spec/shared/bin/s3-upload +69 -0
  54. data/spec/shared/lib/mrss/cluster_config.rb +8 -3
  55. data/spec/shared/lib/mrss/constraints.rb +49 -10
  56. data/spec/shared/lib/mrss/docker_runner.rb +7 -1
  57. data/spec/shared/lib/mrss/event_subscriber.rb +200 -0
  58. data/spec/shared/lib/mrss/server_version_registry.rb +17 -12
  59. data/spec/shared/lib/mrss/spec_organizer.rb +29 -2
  60. data/spec/shared/share/Dockerfile.erb +127 -35
  61. data/spec/shared/share/haproxy-1.conf +16 -0
  62. data/spec/shared/share/haproxy-2.conf +17 -0
  63. data/spec/shared/shlib/server.sh +100 -23
  64. data/spec/shared/shlib/set_env.sh +4 -1
  65. data/spec/spec_helper.rb +1 -1
  66. data/spec/support/models/address.rb +4 -0
  67. data/spec/support/models/mop.rb +10 -0
  68. data/spec/support/models/person.rb +9 -0
  69. data.tar.gz.sig +0 -0
  70. metadata +549 -519
  71. metadata.gz.sig +0 -0
@@ -52,7 +52,7 @@ module Mrss
52
52
  end
53
53
 
54
54
  def require_topology(*topologies)
55
- invalid_topologies = topologies - [:single, :replica_set, :sharded]
55
+ invalid_topologies = topologies - [:single, :replica_set, :sharded, :load_balanced]
56
56
 
57
57
  unless invalid_topologies.empty?
58
58
  raise ArgumentError, "Invalid topologies requested: #{invalid_topologies.join(', ')}"
@@ -82,7 +82,7 @@ module Mrss
82
82
  unless ClusterConfig.instance.server_version >= '4.0'
83
83
  skip 'Transactions tests in a replica set topology require server 4.0+'
84
84
  end
85
- when :sharded
85
+ when :sharded, :load_balanced
86
86
  unless ClusterConfig.instance.server_version >= '4.2'
87
87
  skip 'Transactions tests in a sharded cluster topology require server 4.2+'
88
88
  end
@@ -113,6 +113,14 @@ module Mrss
113
113
  end
114
114
  end
115
115
 
116
+ def require_retry_writes
117
+ before(:all) do
118
+ unless SpecConfig.instance.retry_writes?
119
+ skip "Retry writes is disabled"
120
+ end
121
+ end
122
+ end
123
+
116
124
  def require_no_retry_writes
117
125
  before(:all) do
118
126
  if SpecConfig.instance.retry_writes?
@@ -241,31 +249,52 @@ module Mrss
241
249
  # (mongos 4.0+ overrides the write concern)
242
250
  def require_set_write_concern
243
251
  before(:all) do
244
- if ClusterConfig.instance.topology == :sharded && ClusterConfig.instance.short_server_version >= '4.0'
252
+ if %i(sharded load_balanced).include?(ClusterConfig.instance.topology) &&
253
+ ClusterConfig.instance.short_server_version >= '4.0'
254
+ then
245
255
  skip "mongos 4.0+ overrides write concern"
246
256
  end
247
257
  end
248
258
  end
249
259
 
250
- def require_multi_shard
260
+ def require_multi_mongos
251
261
  before(:all) do
252
262
  if ClusterConfig.instance.topology == :sharded && SpecConfig.instance.addresses.length == 1
253
- skip 'Test requires a minimum of two shards if run in sharded topology'
263
+ skip 'Test requires a minimum of two mongoses if run in sharded topology'
264
+ end
265
+
266
+ if ClusterConfig.instance.topology == :load_balanced && SpecConfig.instance.single_mongos?
267
+ skip 'Test requires a minimum of two mongoses if run in load-balanced topology'
254
268
  end
255
269
  end
256
270
  end
257
271
 
258
- def require_no_multi_shard
272
+ # In sharded topology operations are distributed to the mongoses.
273
+ # When we set fail points, the fail point may be set on one mongos and
274
+ # operation may be executed on another mongos, causing failures.
275
+ # Tests that are not setting targeted fail points should utilize this
276
+ # method to restrict themselves to single mongos.
277
+ #
278
+ # In load-balanced topology, the same problem can happen when there is
279
+ # more than one mongos behind the load balancer.
280
+ def require_no_multi_mongos
259
281
  before(:all) do
260
282
  if ClusterConfig.instance.topology == :sharded && SpecConfig.instance.addresses.length > 1
261
- skip 'Test requires a single shard if run in sharded topology'
283
+ skip 'Test requires a single mongos if run in sharded topology'
284
+ end
285
+ if ClusterConfig.instance.topology == :load_balanced && !SpecConfig.instance.single_mongos?
286
+ skip 'Test requires a single mongos, as indicated by SINGLE_MONGOS=1 environment variable, if run in load-balanced topology'
262
287
  end
263
288
  end
264
289
  end
265
290
 
291
+ alias :require_no_multi_shard :require_no_multi_mongos
292
+
266
293
  def require_wired_tiger
267
294
  before(:all) do
268
- if ClusterConfig.instance.storage_engine != :wired_tiger
295
+ # Storage detection fails for serverless instances. However, it is safe to
296
+ # assume that a serverless instance uses WiredTiger Storage Engine.
297
+ if !SpecConfig.instance.serverless? && ClusterConfig.instance.storage_engine != :wired_tiger
269
298
  skip 'Test requires WiredTiger storage engine'
270
299
  end
271
300
  end
@@ -274,7 +303,9 @@ module Mrss
274
303
  def require_wired_tiger_on_36
275
304
  before(:all) do
276
305
  if ClusterConfig.instance.short_server_version >= '3.6'
277
- if ClusterConfig.instance.storage_engine != :wired_tiger
306
+ # Storage detection fails for serverless instances. However, it is safe to
307
+ # assume that a serverless instance uses WiredTiger Storage Engine.
308
+ if !SpecConfig.instance.serverless? && ClusterConfig.instance.storage_engine != :wired_tiger
278
309
  skip 'Test requires WiredTiger storage engine on 3.6+ servers'
279
310
  end
280
311
  end
@@ -283,7 +314,7 @@ module Mrss
283
314
 
284
315
  def require_mmapv1
285
316
  before(:all) do
286
- if ClusterConfig.instance.storage_engine != :mmapv1
317
+ if SpecConfig.instance.serverless? || ClusterConfig.instance.storage_engine != :mmapv1
287
318
  skip 'Test requires MMAPv1 storage engine'
288
319
  end
289
320
  end
@@ -342,5 +373,13 @@ module Mrss
342
373
  end
343
374
  end
344
375
  end
376
+
377
+ def require_unix_socket
378
+ before(:all) do
379
+ if ENV['TOPOLOGY'] == 'load-balanced'
380
+ skip 'Load balancer does not listen on Unix sockets'
381
+ end
382
+ end
383
+ end
345
384
  end
346
385
  end
@@ -111,7 +111,7 @@ module Mrss
111
111
  '.'])
112
112
  end
113
113
 
114
- BASE_TEST_COMMAND = %w(docker run -i --tmpfs /tmpfs:exec).freeze
114
+ BASE_TEST_COMMAND = %w(docker run --rm -i --tmpfs /tmpfs:exec).freeze
115
115
 
116
116
  def run_tests
117
117
  run_command(BASE_TEST_COMMAND + tty_arg + extra_env + [image_tag] +
@@ -179,9 +179,11 @@ module Mrss
179
179
  BASE_IMAGES = {
180
180
  'debian81' => 'debian:jessie',
181
181
  'debian92' => 'debian:stretch',
182
+ 'debian10' => 'debian:buster',
182
183
  'ubuntu1404' => 'ubuntu:trusty',
183
184
  'ubuntu1604' => 'ubuntu:xenial',
184
185
  'ubuntu1804' => 'ubuntu:bionic',
186
+ 'ubuntu2004' => 'ubuntu:focal',
185
187
  'rhel62' => 'centos:6',
186
188
  'rhel70' => 'centos:7',
187
189
  }.freeze
@@ -198,6 +200,10 @@ module Mrss
198
200
  ruby == 'ruby-head'
199
201
  end
200
202
 
203
+ def system_ruby?
204
+ %w(1 true yes).include?(@env['SYSTEM_RUBY']&.downcase)
205
+ end
206
+
201
207
  def server_version
202
208
  @env['MONGODB_VERSION']
203
209
  end
@@ -0,0 +1,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mrss
4
+ # Test event subscriber.
5
+ class EventSubscriber
6
+
7
+ # The mappings of event names to types.
8
+ MAPPINGS = {
9
+ 'topology_opening_event' => Mongo::Monitoring::Event::TopologyOpening,
10
+ 'topology_description_changed_event' => Mongo::Monitoring::Event::TopologyChanged,
11
+ 'topology_closed_event' => Mongo::Monitoring::Event::TopologyClosed,
12
+ 'server_opening_event' => Mongo::Monitoring::Event::ServerOpening,
13
+ 'server_description_changed_event' => Mongo::Monitoring::Event::ServerDescriptionChanged,
14
+ 'server_closed_event' => Mongo::Monitoring::Event::ServerClosed
15
+ }.freeze
16
+
17
+ attr_reader :all_events
18
+
19
+ attr_reader :started_events
20
+
21
+ attr_reader :succeeded_events
22
+
23
+ attr_reader :failed_events
24
+
25
+ attr_reader :published_events
26
+
27
+ # @param [ String ] name Optional name for the event subscriber.
28
+ def initialize(name: nil)
29
+ @mutex = Mutex.new
30
+ clear_events!
31
+ @name = name
32
+ end
33
+
34
+ def to_s
35
+ %Q`#<EventSubscriber:#{@name ? "\"#{@name}\"" : '%x' % object_id} \
36
+ started=#{started_events.length} \
37
+ succeeded=#{succeeded_events.length} \
38
+ failed=#{failed_events.length} \
39
+ published=#{published_events.length}>`
40
+ end
41
+
42
+ alias :inspect :to_s
43
+
44
+ # Event retrieval
45
+
46
+ def select_started_events(cls)
47
+ started_events.select do |event|
48
+ event.is_a?(cls)
49
+ end
50
+ end
51
+
52
+ def select_succeeded_events(cls)
53
+ succeeded_events.select do |event|
54
+ event.is_a?(cls)
55
+ end
56
+ end
57
+
58
+ def select_completed_events(*classes)
59
+ (succeeded_events + failed_events).select do |event|
60
+ classes.any? { |c| c === event }
61
+ end
62
+ end
63
+
64
+ def select_published_events(cls)
65
+ published_events.select do |event|
66
+ event.is_a?(cls)
67
+ end
68
+ end
69
+
70
+ # Filters command started events for the specified command name.
71
+ def command_started_events(command_name)
72
+ started_events.select do |event|
73
+ event.command[command_name]
74
+ end
75
+ end
76
+
77
+ def non_auth_command_started_events
78
+ started_events.reject do |event|
79
+ %w(authenticate getnonce saslSstart saslContinue).any? do |cmd|
80
+ event.command[cmd]
81
+ end
82
+ end
83
+ end
84
+
85
+ # Locates command stated events for the specified command name,
86
+ # asserts that there is exactly one such event, and returns it.
87
+ def single_command_started_event(command_name, include_auth: false)
88
+ events = if include_auth
89
+ started_events
90
+ else
91
+ non_auth_command_started_events
92
+ end
93
+ events.select! do |event|
94
+ event.command[command_name]
95
+ end
96
+ if events.length != 1
97
+ raise "Expected a single #{command_name} event but we have #{events.length}"
98
+ end
99
+ events.first
100
+ end
101
+
102
+
103
+ # Get the first succeeded event published for the name, and then delete it.
104
+ #
105
+ # @param [ String ] name The event name.
106
+ #
107
+ # @return [ Event ] The matching event.
108
+ def first_event(name)
109
+ cls = MAPPINGS[name]
110
+ if cls.nil?
111
+ raise ArgumentError, "Bogus event name #{name}"
112
+ end
113
+ matching = succeeded_events.find do |event|
114
+ cls === event
115
+ end
116
+ succeeded_events.delete(matching)
117
+ matching
118
+ end
119
+
120
+ # Event recording
121
+
122
+ # Cache the started event.
123
+ #
124
+ # @param [ Event ] event The event.
125
+ def started(event)
126
+ @mutex.synchronize do
127
+ started_events << event
128
+ all_events << event
129
+ end
130
+ end
131
+
132
+ # Cache the succeeded event.
133
+ #
134
+ # @param [ Event ] event The event.
135
+ def succeeded(event)
136
+ @mutex.synchronize do
137
+ succeeded_events << event
138
+ all_events << event
139
+ end
140
+ end
141
+
142
+ # Cache the failed event.
143
+ #
144
+ # @param [ Event ] event The event.
145
+ def failed(event)
146
+ @mutex.synchronize do
147
+ failed_events << event
148
+ all_events << event
149
+ end
150
+ end
151
+
152
+ def published(event)
153
+ @mutex.synchronize do
154
+ published_events << event
155
+ all_events << event
156
+ end
157
+ end
158
+
159
+ # Clear all cached events.
160
+ def clear_events!
161
+ @all_events = []
162
+ @started_events = []
163
+ @succeeded_events = []
164
+ @failed_events = []
165
+ @published_events = []
166
+ self
167
+ end
168
+ end
169
+ # Only handles succeeded events correctly.
170
+ class PhasedEventSubscriber < EventSubscriber
171
+ def initialize
172
+ super
173
+ @phase_events = {}
174
+ end
175
+
176
+ def phase_finished(phase_index)
177
+ @phase_events[phase_index] = succeeded_events
178
+ @succeeded_events = []
179
+ end
180
+
181
+ def phase_events(phase_index)
182
+ @phase_events[phase_index]
183
+ end
184
+
185
+ def event_count
186
+ @phase_events.inject(0) do |sum, event|
187
+ sum + event.length
188
+ end
189
+ end
190
+ end
191
+
192
+ class VerboseEventSubscriber < EventSubscriber
193
+ %w(started succeeded failed published).each do |meth|
194
+ define_method(meth) do |event|
195
+ puts event.summary
196
+ super(event)
197
+ end
198
+ end
199
+ end
200
+ end
@@ -48,19 +48,24 @@ module Mrss
48
48
  url = dl['archive']['url']
49
49
  end
50
50
  rescue MissingDownloadUrl
51
- if %w(4.7 4.7.0).include?(desired_version)
52
- # 4.7.0 has no advertised downloads but it is downloadable and
53
- # we do need it. Dirty hack below.
54
- registry = self.class.new('4.4.3', arch)
55
- registry.download_url.sub('4.4.3', '4.7.0').tap do |url|
56
- # Sanity check - ensure the URL we hacked up is a valid one
57
- io = uri_open(url)
58
- begin
59
- io.read(1)
60
- ensure
61
- io.close
62
- end
51
+ if %w(2.6 3.0).include?(desired_version) && arch == 'ubuntu1604'
52
+ # 2.6 and 3.0 are only available for ubuntu1204 and ubuntu1404.
53
+ # Those ubuntus have ancient Pythons that don't work due to not
54
+ # implementing recent TLS protocols.
55
+ # Because of this we test on ubuntu1604 which has a newer Python.
56
+ # But we still need to retrieve ubuntu1404-targeting builds.
57
+ url = self.class.new('3.2', arch).download_url
58
+ unless url.include?('3.2.')
59
+ raise 'URL not in expected format'
63
60
  end
61
+ url = case desired_version
62
+ when '2.6'
63
+ url.sub(/\b3\.2\.\d+/, '2.6.12')
64
+ when '3.0'
65
+ url.sub(/\b3\.2\.\d+/, '3.0.15')
66
+ else
67
+ raise NotImplementedError
68
+ end.sub('ubuntu1604', 'ubuntu1404')
64
69
  else
65
70
  raise
66
71
  end
@@ -25,18 +25,28 @@ module Mrss
25
25
  end
26
26
 
27
27
  def initialize(root: nil, classifiers:, priority_order:,
28
- spec_root: nil, rspec_json_path: nil, rspec_all_json_path: nil
28
+ spec_root: nil, rspec_json_path: nil, rspec_all_json_path: nil,
29
+ randomize: false
29
30
  )
30
31
  @spec_root = spec_root || File.join(root, 'spec')
31
32
  @classifiers = classifiers
32
33
  @priority_order = priority_order
33
34
  @rspec_json_path = rspec_json_path || File.join(root, 'tmp/rspec.json')
34
35
  @rspec_all_json_path = rspec_all_json_path || File.join(root, 'tmp/rspec-all.json')
36
+ @randomize = !!randomize
35
37
  end
36
38
 
37
39
  attr_reader :spec_root, :classifiers, :priority_order
38
40
  attr_reader :rspec_json_path, :rspec_all_json_path
39
41
 
42
+ def randomize?
43
+ @randomize
44
+ end
45
+
46
+ def seed
47
+ @seed ||= (rand * 100_000).to_i
48
+ end
49
+
40
50
  def buckets
41
51
  @buckets ||= {}.tap do |buckets|
42
52
  Find.find(spec_root) do |path|
@@ -81,10 +91,20 @@ module Mrss
81
91
  end
82
92
 
83
93
  def run
94
+ run_buckets(*buckets.keys)
95
+ end
96
+
97
+ def run_buckets(*buckets)
84
98
  FileUtils.rm_f(rspec_all_json_path)
85
99
 
100
+ buckets.each do |bucket|
101
+ if bucket && !self.buckets[bucket]
102
+ raise "Unknown bucket #{bucket}"
103
+ end
104
+ end
105
+ buckets = Hash[self.buckets.select { |k, v| buckets.include?(k) }]
106
+
86
107
  failed = []
87
- buckets = self.buckets.dup
88
108
 
89
109
  priority_order.each do |category|
90
110
  if files = buckets.delete(category)
@@ -112,8 +132,12 @@ module Mrss
112
132
  puts "Running #{category.to_s.gsub('_', ' ')} tests"
113
133
  FileUtils.rm_f(rspec_json_path)
114
134
  cmd = %w(rspec) + paths
135
+ if randomize?
136
+ cmd += %W(--order rand:#{seed})
137
+ end
115
138
 
116
139
  begin
140
+ puts "Running #{cmd.join(' ')}"
117
141
  ChildProcessHelper.check_call(cmd)
118
142
  ensure
119
143
  if File.exist?(rspec_json_path)
@@ -139,6 +163,9 @@ module Mrss
139
163
  end
140
164
  new.delete('version')
141
165
  new.delete('summary_line')
166
+ # The spec organizer runs all buckets with the same seed, hence
167
+ # we can drop the seed from new results.
168
+ new.delete('seed')
142
169
  unless new.empty?
143
170
  raise "Unhandled rspec results keys: #{new.keys.join(', ')}"
144
171
  end