mongoid 7.2.0 → 7.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/README.md +1 -1
  5. data/lib/mongoid/attributes.rb +8 -1
  6. data/lib/mongoid/criteria/queryable/selector.rb +0 -4
  7. data/lib/mongoid/document.rb +3 -2
  8. data/lib/mongoid/errors/mongoid_error.rb +1 -1
  9. data/lib/mongoid/interceptable.rb +3 -1
  10. data/lib/mongoid/matcher.rb +19 -43
  11. data/lib/mongoid/matcher/elem_match.rb +2 -1
  12. data/lib/mongoid/matcher/expression.rb +5 -14
  13. data/lib/mongoid/matcher/field_expression.rb +4 -5
  14. data/lib/mongoid/matcher/field_operator.rb +7 -11
  15. data/lib/mongoid/reloadable.rb +5 -0
  16. data/lib/mongoid/validatable/associated.rb +1 -1
  17. data/lib/mongoid/validatable/presence.rb +3 -3
  18. data/lib/mongoid/validatable/uniqueness.rb +1 -1
  19. data/lib/mongoid/version.rb +1 -1
  20. data/lib/rails/generators/mongoid/config/config_generator.rb +8 -1
  21. data/lib/rails/generators/mongoid/config/templates/mongoid.yml +1 -1
  22. data/spec/integration/app_spec.rb +174 -84
  23. data/spec/integration/callbacks_models.rb +49 -0
  24. data/spec/integration/callbacks_spec.rb +216 -0
  25. data/spec/integration/document_spec.rb +21 -0
  26. data/spec/integration/matcher_operator_data/elem_match.yml +46 -0
  27. data/spec/integration/matcher_operator_data/gt_types.yml +63 -0
  28. data/spec/integration/matcher_operator_data/gte_types.yml +15 -0
  29. data/spec/integration/matcher_operator_data/implicit_traversal.yml +96 -0
  30. data/spec/integration/matcher_operator_data/lt_types.yml +15 -0
  31. data/spec/integration/matcher_operator_data/lte_types.yml +15 -0
  32. data/spec/integration/matcher_operator_data/ne_types.yml +15 -0
  33. data/spec/lite_spec_helper.rb +3 -4
  34. data/spec/mongoid/association/embedded/embedded_in/proxy_spec.rb +50 -0
  35. data/spec/mongoid/atomic/paths_spec.rb +41 -0
  36. data/spec/mongoid/attributes_spec.rb +241 -0
  37. data/spec/mongoid/contextual/atomic_spec.rb +17 -4
  38. data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +36 -0
  39. data/spec/mongoid/document_fields_spec.rb +26 -0
  40. data/spec/mongoid/document_query_spec.rb +51 -0
  41. data/spec/mongoid/errors/mongoid_error_spec.rb +20 -8
  42. data/spec/mongoid/matcher/extract_attribute_data/numeric_keys.yml +104 -0
  43. data/spec/mongoid/matcher/extract_attribute_data/traversal.yml +68 -88
  44. data/spec/mongoid/matcher/extract_attribute_spec.rb +3 -13
  45. data/spec/mongoid/persistable/settable_spec.rb +30 -0
  46. data/spec/mongoid/persistable_spec.rb +2 -2
  47. data/spec/shared/bin/get-mongodb-download-url +17 -0
  48. data/spec/shared/bin/s3-copy +45 -0
  49. data/spec/shared/bin/s3-upload +69 -0
  50. data/spec/shared/lib/mrss/cluster_config.rb +226 -0
  51. data/spec/shared/lib/mrss/constraints.rb +71 -6
  52. data/spec/shared/lib/mrss/docker_runner.rb +271 -0
  53. data/spec/shared/lib/mrss/lite_constraints.rb +16 -0
  54. data/spec/shared/lib/mrss/server_version_registry.rb +115 -0
  55. data/spec/shared/lib/mrss/spec_organizer.rb +32 -2
  56. data/spec/shared/lib/mrss/utils.rb +15 -0
  57. data/spec/shared/share/Dockerfile.erb +322 -0
  58. data/spec/shared/share/haproxy-1.conf +16 -0
  59. data/spec/shared/share/haproxy-2.conf +17 -0
  60. data/spec/shared/shlib/distro.sh +73 -0
  61. data/spec/shared/shlib/server.sh +317 -0
  62. data/spec/shared/shlib/set_env.sh +131 -0
  63. data/spec/spec_helper.rb +1 -1
  64. data/spec/support/models/customer.rb +11 -0
  65. data/spec/support/models/customer_address.rb +12 -0
  66. data/spec/support/models/dictionary.rb +6 -0
  67. data/spec/support/models/mop.rb +10 -0
  68. data/spec/support/spec_config.rb +8 -0
  69. metadata +554 -508
  70. metadata.gz.sig +3 -2
@@ -0,0 +1,271 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+
4
+ require 'optparse'
5
+ require 'erb'
6
+ autoload :Dotenv, 'dotenv'
7
+
8
+ module Mrss
9
+ autoload :ServerVersionRegistry, 'mrss/server_version_registry'
10
+
11
+ class DockerRunner
12
+ def initialize(**opts)
13
+ # These options are required:
14
+ opts.fetch(:image_tag)
15
+ opts.fetch(:dockerfile_path)
16
+ opts.fetch(:default_script)
17
+ opts.fetch(:project_lib_subdir)
18
+
19
+ @options = opts
20
+ end
21
+
22
+ attr_reader :options
23
+
24
+ def run
25
+ process_arguments
26
+ unless @options[:exec_only]
27
+ create_dockerfile
28
+ create_image
29
+ end
30
+ if @options[:mongo_only]
31
+ run_deployment
32
+ else
33
+ run_tests
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def process_arguments
40
+ #@options = {}
41
+ OptionParser.new do |opts|
42
+ opts.banner = "Usage: test-on-docker [-d distro] [evergreen_key=value ...]"
43
+
44
+ opts.on("-a", "--add-env=PATH", "Load environment variables from PATH in .env format") do |path|
45
+ @options[:extra_env] ||= {}
46
+ unless File.exist?(path)
47
+ raise "-a option references nonexistent file #{path}"
48
+ end
49
+ Dotenv.parse(path).each do |k, v|
50
+ @options[:extra_env][k] = v
51
+ end
52
+ end
53
+
54
+ opts.on("-d", "--distro=DISTRO", "Distro to use") do |v|
55
+ @options[:distro] = v
56
+ end
57
+
58
+ opts.on('-e', '--exec-only', 'Execute tests using existing Dockerfile (for offline user)') do |v|
59
+ @options[:exec_only] = v
60
+ end
61
+
62
+ opts.on('-m', '--mongo-only=PORT', 'Start the MongoDB deployment and expose it to host on ports starting with PORT') do |v|
63
+ @options[:mongo_only] = v.to_i
64
+ end
65
+
66
+ opts.on('-p', '--preload', 'Preload Ruby toolchain and server binaries in docker') do |v|
67
+ @options[:preload] = v
68
+ end
69
+
70
+ opts.on('-s', '--script=SCRIPT', 'Test script to invoke') do |v|
71
+ @options[:script] = v
72
+ end
73
+
74
+ opts.on('-i', '--interactive', 'Interactive mode - disable per-test timeouts') do |v|
75
+ @options[:interactive] = v
76
+ end
77
+ end.parse!
78
+
79
+ @env = Hash[ARGV.map do |arg|
80
+ arg.split('=', 2)
81
+ end]
82
+
83
+ @env['RVM_RUBY'] ||= 'ruby-2.7'
84
+ unless ruby =~ /^j?ruby-/
85
+ raise "RVM_RUBY option is not in expected format: #{ruby}"
86
+ end
87
+
88
+ @env['MONGODB_VERSION'] ||= '4.4'
89
+ end
90
+
91
+ def create_dockerfile
92
+ template_path = File.join(File.dirname(__FILE__), '../../share/Dockerfile.erb')
93
+ result = ERB.new(File.read(template_path)).result(binding)
94
+ File.open(dockerfile_path, 'w') do |f|
95
+ f << result
96
+ end
97
+ end
98
+
99
+ def image_tag
100
+ options.fetch(:image_tag)
101
+ end
102
+
103
+ def dockerfile_path
104
+ options.fetch(:dockerfile_path)
105
+ end
106
+
107
+ def create_image
108
+ run_command(['docker', 'build',
109
+ '-t', image_tag,
110
+ '-f', dockerfile_path,
111
+ '.'])
112
+ end
113
+
114
+ BASE_TEST_COMMAND = %w(docker run --rm -i --tmpfs /tmpfs:exec).freeze
115
+
116
+ def run_tests
117
+ run_command(BASE_TEST_COMMAND + tty_arg + extra_env + [image_tag] +
118
+ script.split(/\s+/))
119
+ end
120
+
121
+ def run_deployment
122
+ run_command(BASE_TEST_COMMAND + tty_arg + extra_env + [
123
+ '-e', %q`TEST_CMD=watch -x bash -c "ps awwxu |egrep 'mongo|ocsp'"`,
124
+ '-e', 'BIND_ALL=true',
125
+ ] + port_forwards + [image_tag] + script.split(/\s+/))
126
+ end
127
+
128
+ def tty_arg
129
+ tty = File.open('/dev/stdin') do |f|
130
+ f.isatty
131
+ end
132
+ if tty
133
+ %w(-t --init)
134
+ else
135
+ []
136
+ end
137
+ end
138
+
139
+ def extra_env
140
+ if @options[:extra_env]
141
+ @options[:extra_env].map do |k, v|
142
+ # Here the value must not be escaped
143
+ ['-e', "#{k}=#{v}"]
144
+ end.flatten
145
+ else
146
+ []
147
+ end
148
+ end
149
+
150
+ def port_forwards
151
+ args = (0...num_exposed_ports).map do |i|
152
+ host_port = @options[:mongo_only] + i
153
+ container_port = 27017 + i
154
+ ['-p', "#{host_port}:#{container_port}"]
155
+ end.flatten
156
+
157
+ if @env['OCSP_ALGORITHM'] && !@env['OCSP_VERIFIER']
158
+ args += %w(-p 8100:8100)
159
+ end
160
+
161
+ args
162
+ end
163
+
164
+ def run_command(cmd)
165
+ if pid = fork
166
+ Process.wait(pid)
167
+ unless $?.exitstatus == 0
168
+ raise "Process exited with code #{$?.exitstatus}"
169
+ end
170
+ else
171
+ exec(*cmd)
172
+ end
173
+ end
174
+
175
+ def distro
176
+ @options[:distro] || 'ubuntu1604'
177
+ end
178
+
179
+ BASE_IMAGES = {
180
+ 'debian81' => 'debian:jessie',
181
+ 'debian92' => 'debian:stretch',
182
+ 'debian10' => 'debian:buster',
183
+ 'ubuntu1404' => 'ubuntu:trusty',
184
+ 'ubuntu1604' => 'ubuntu:xenial',
185
+ 'ubuntu1804' => 'ubuntu:bionic',
186
+ 'ubuntu2004' => 'ubuntu:focal',
187
+ 'rhel62' => 'centos:6',
188
+ 'rhel70' => 'centos:7',
189
+ }.freeze
190
+
191
+ def base_image
192
+ BASE_IMAGES[distro] or raise "Unknown distro: #{distro}"
193
+ end
194
+
195
+ def ruby
196
+ @env['RVM_RUBY']
197
+ end
198
+
199
+ def ruby_head?
200
+ ruby == 'ruby-head'
201
+ end
202
+
203
+ def system_ruby?
204
+ %w(1 true yes).include?(@env['SYSTEM_RUBY']&.downcase)
205
+ end
206
+
207
+ def server_version
208
+ @env['MONGODB_VERSION']
209
+ end
210
+
211
+ def script
212
+ @options[:script] || options.fetch(:default_script)
213
+ end
214
+
215
+ def debian?
216
+ distro =~ /debian|ubuntu/
217
+ end
218
+
219
+ def preload?
220
+ !!@options[:preload]
221
+ end
222
+
223
+ def interactive?
224
+ !!@options[:interactive]
225
+ end
226
+
227
+ def project_lib_subdir
228
+ options.fetch(:project_lib_subdir)
229
+ end
230
+
231
+ def server_download_url
232
+ @server_download_url ||= ServerVersionRegistry.new(server_version, distro).download_url
233
+ end
234
+
235
+ def libmongocrypt_path
236
+ case distro
237
+ when /ubuntu1604/
238
+ "./ubuntu1604/nocrypto/lib64/libmongocrypt.so"
239
+ when /ubuntu1804/
240
+ "./ubuntu1804-64/nocrypto/lib64/libmongocrypt.so"
241
+ when /debian92/
242
+ "./debian92/nocrypto/lib64/libmongocrypt.so"
243
+ else
244
+ raise "This script does not support running FLE tests on #{distro}. Use ubuntu1604, ubuntu1804 or debian92 instead"
245
+ end
246
+ end
247
+
248
+ def expose?
249
+ !!@options[:mongo_only]
250
+ end
251
+
252
+ def fle?
253
+ %w(1 true yes).include?(@env['FLE']&.downcase)
254
+ end
255
+
256
+ def num_exposed_ports
257
+ case @env['TOPOLOGY'] || 'standalone'
258
+ when 'standalone'
259
+ 1
260
+ when 'replica-set'
261
+ 3
262
+ when 'sharded-cluster'
263
+ if @env['SINGLE_MONGOS']
264
+ 1
265
+ else
266
+ 2
267
+ end
268
+ end
269
+ end
270
+ end
271
+ end
@@ -171,5 +171,21 @@ module Mrss
171
171
  end
172
172
  end
173
173
  end
174
+
175
+ def require_active_support
176
+ before(:all) do
177
+ if !SpecConfig.instance.active_support?
178
+ skip 'This test requires ActiveSupport; set WITH_ACTIVE_SUPPORT=1 in environment'
179
+ end
180
+ end
181
+ end
182
+
183
+ def no_active_support
184
+ before(:all) do
185
+ if SpecConfig.instance.active_support?
186
+ skip 'This test requires no ActiveSupport; unset WITH_ACTIVE_SUPPORT in environment'
187
+ end
188
+ end
189
+ end
174
190
  end
175
191
  end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+
4
+ autoload :JSON, 'json'
5
+ require 'open-uri'
6
+
7
+ module Mrss
8
+ class ServerVersionRegistry
9
+ class Error < StandardError
10
+ end
11
+
12
+ class UnknownVersion < Error
13
+ end
14
+
15
+ class MissingDownloadUrl < Error
16
+ end
17
+
18
+ class BrokenDownloadUrl < Error
19
+ end
20
+
21
+ def initialize(desired_version, arch)
22
+ @desired_version, @arch = desired_version, arch
23
+ end
24
+
25
+ attr_reader :desired_version, :arch
26
+
27
+ def download_url
28
+ @download_url ||= begin
29
+ version, version_ok = detect_version(current_catalog)
30
+ if version.nil?
31
+ version, full_version_ok = detect_version(full_catalog)
32
+ version_ok ||= full_version_ok
33
+ end
34
+ if version.nil?
35
+ if version_ok
36
+ raise MissingDownloadUrl, "No downloads for version #{desired_version}"
37
+ else
38
+ raise UnknownVersion, "No version #{desired_version}"
39
+ end
40
+ end
41
+ dl = version['downloads'].detect do |dl|
42
+ dl['archive']['url'].index("enterprise-#{arch}") &&
43
+ dl['arch'] == 'x86_64'
44
+ end
45
+ unless dl
46
+ raise MissingDownloadUrl, "No download for #{arch} for #{version['version']}"
47
+ end
48
+ url = dl['archive']['url']
49
+ end
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
63
+ end
64
+ else
65
+ raise
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def uri_open(*args)
72
+ if RUBY_VERSION < '2.5'
73
+ open(*args)
74
+ else
75
+ URI.open(*args)
76
+ end
77
+ end
78
+
79
+ def detect_version(catalog)
80
+ candidate_versions = catalog['versions'].select do |version|
81
+ version['version'].start_with?(desired_version) &&
82
+ !version['version'].include?('-')
83
+ end
84
+ version_ok = !candidate_versions.empty?
85
+ # Sometimes the download situation is borked and there is a release
86
+ # with no downloads... skip those.
87
+ version = candidate_versions.detect do |version|
88
+ !version['downloads'].empty?
89
+ end
90
+ # Allow RC releases if there isn't a GA release.
91
+ if version.nil?
92
+ candidate_versions = catalog['versions'].select do |version|
93
+ version['version'].start_with?(desired_version)
94
+ end
95
+ version_ok ||= !candidate_versions.empty?
96
+ version = candidate_versions.detect do |version|
97
+ !version['downloads'].empty?
98
+ end
99
+ end
100
+ [version, version_ok]
101
+ end
102
+
103
+ def current_catalog
104
+ @current_catalog ||= begin
105
+ JSON.load(uri_open('http://downloads.mongodb.org/current.json').read)
106
+ end
107
+ end
108
+
109
+ def full_catalog
110
+ @full_catalog ||= begin
111
+ JSON.load(uri_open('http://downloads.mongodb.org/full.json').read)
112
+ end
113
+ end
114
+ end
115
+ end
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+
1
4
  autoload :JSON, 'json'
2
5
  autoload :FileUtils, 'fileutils'
3
6
  autoload :Find, 'find'
@@ -22,18 +25,28 @@ module Mrss
22
25
  end
23
26
 
24
27
  def initialize(root: nil, classifiers:, priority_order:,
25
- 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
26
30
  )
27
31
  @spec_root = spec_root || File.join(root, 'spec')
28
32
  @classifiers = classifiers
29
33
  @priority_order = priority_order
30
34
  @rspec_json_path = rspec_json_path || File.join(root, 'tmp/rspec.json')
31
35
  @rspec_all_json_path = rspec_all_json_path || File.join(root, 'tmp/rspec-all.json')
36
+ @randomize = !!randomize
32
37
  end
33
38
 
34
39
  attr_reader :spec_root, :classifiers, :priority_order
35
40
  attr_reader :rspec_json_path, :rspec_all_json_path
36
41
 
42
+ def randomize?
43
+ @randomize
44
+ end
45
+
46
+ def seed
47
+ @seed ||= (rand * 100_000).to_i
48
+ end
49
+
37
50
  def buckets
38
51
  @buckets ||= {}.tap do |buckets|
39
52
  Find.find(spec_root) do |path|
@@ -78,10 +91,20 @@ module Mrss
78
91
  end
79
92
 
80
93
  def run
94
+ run_buckets(*buckets.keys)
95
+ end
96
+
97
+ def run_buckets(*buckets)
81
98
  FileUtils.rm_f(rspec_all_json_path)
82
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
+
83
107
  failed = []
84
- buckets = self.buckets.dup
85
108
 
86
109
  priority_order.each do |category|
87
110
  if files = buckets.delete(category)
@@ -109,8 +132,12 @@ module Mrss
109
132
  puts "Running #{category.to_s.gsub('_', ' ')} tests"
110
133
  FileUtils.rm_f(rspec_json_path)
111
134
  cmd = %w(rspec) + paths
135
+ if randomize?
136
+ cmd += %W(--order rand:#{seed})
137
+ end
112
138
 
113
139
  begin
140
+ puts "Running #{cmd.join(' ')}"
114
141
  ChildProcessHelper.check_call(cmd)
115
142
  ensure
116
143
  if File.exist?(rspec_json_path)
@@ -136,6 +163,9 @@ module Mrss
136
163
  end
137
164
  new.delete('version')
138
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')
139
169
  unless new.empty?
140
170
  raise "Unhandled rspec results keys: #{new.keys.join(', ')}"
141
171
  end