bson 5.0.2 → 5.1.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/Rakefile +2 -0
- data/ext/bson/extconf.rb +1 -1
- data/lib/bson/binary.rb +126 -4
- data/lib/bson/document.rb +8 -0
- data/lib/bson/object_id.rb +1 -1
- data/lib/bson/regexp.rb +3 -3
- data/lib/bson/vector.rb +44 -0
- data/lib/bson/version.rb +5 -16
- data/lib/bson.rb +1 -0
- data/spec/bson/document_as_spec.rb +14 -0
- data/spec/bson/vector_spec.rb +33 -0
- data/spec/runners/binary_vector.rb +78 -0
- data/spec/shared/LICENSE +20 -0
- data/spec/shared/bin/get-mongodb-download-url +17 -0
- data/spec/shared/bin/s3-copy +45 -0
- data/spec/shared/bin/s3-upload +69 -0
- data/spec/shared/lib/mrss/child_process_helper.rb +80 -0
- data/spec/shared/lib/mrss/cluster_config.rb +231 -0
- data/spec/shared/lib/mrss/constraints.rb +378 -0
- data/spec/shared/lib/mrss/docker_runner.rb +298 -0
- data/spec/shared/lib/mrss/eg_config_utils.rb +51 -0
- data/spec/shared/lib/mrss/event_subscriber.rb +210 -0
- data/spec/shared/lib/mrss/lite_constraints.rb +238 -0
- data/spec/shared/lib/mrss/release/candidate.rb +284 -0
- data/spec/shared/lib/mrss/release/product_data.rb +144 -0
- data/spec/shared/lib/mrss/server_version_registry.rb +113 -0
- data/spec/shared/lib/mrss/session_registry.rb +69 -0
- data/spec/shared/lib/mrss/session_registry_legacy.rb +60 -0
- data/spec/shared/lib/mrss/spec_organizer.rb +179 -0
- data/spec/shared/lib/mrss/utils.rb +37 -0
- data/spec/shared/lib/tasks/candidate.rake +64 -0
- data/spec/shared/share/Dockerfile.erb +251 -0
- data/spec/shared/share/haproxy-1.conf +16 -0
- data/spec/shared/share/haproxy-2.conf +17 -0
- data/spec/shared/shlib/config.sh +27 -0
- data/spec/shared/shlib/distro.sh +84 -0
- data/spec/shared/shlib/server.sh +423 -0
- data/spec/shared/shlib/set_env.sh +110 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/spec_tests/binary_vector_spec.rb +82 -0
- data/spec/spec_tests/data/binary_vector/README.md +61 -0
- data/spec/spec_tests/data/binary_vector/float32.json +65 -0
- data/spec/spec_tests/data/binary_vector/int8.json +57 -0
- data/spec/spec_tests/data/binary_vector/packed_bit.json +83 -0
- data/spec/spec_tests/data/corpus/binary.json +30 -0
- metadata +70 -6
@@ -0,0 +1,238 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
module Mrss
|
5
|
+
module LiteConstraints
|
6
|
+
|
7
|
+
# Constrain tests that use TimeoutInterrupt to MRI (and Unix).
|
8
|
+
def require_mri
|
9
|
+
before(:all) do
|
10
|
+
unless SpecConfig.instance.mri?
|
11
|
+
skip "MRI required, we have #{SpecConfig.instance.platform}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def require_jruby
|
17
|
+
before(:all) do
|
18
|
+
unless BSON::Environment.jruby?
|
19
|
+
skip "JRuby required, we have #{SpecConfig.instance.platform}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# This is for marking tests that fail on JRuby that should
|
25
|
+
# in principle work (as opposed to being fundamentally incompatible
|
26
|
+
# with JRuby).
|
27
|
+
# Often times these failures happen only in Evergreen.
|
28
|
+
def fails_on_jruby(version=nil)
|
29
|
+
before(:all) do
|
30
|
+
if BSON::Environment.jruby?
|
31
|
+
if version
|
32
|
+
min_parts = version.split('.').map(&:to_i)
|
33
|
+
actual_parts = JRUBY_VERSION.split('.').map(&:to_i)[0...min_parts.length]
|
34
|
+
actual = actual_parts.join('.')
|
35
|
+
if actual <= version
|
36
|
+
skip "Fails on jruby through #{version}"
|
37
|
+
end
|
38
|
+
else
|
39
|
+
skip "Fails on jruby"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Indicates that the respective test uses the internet in some capacity,
|
46
|
+
# for example the test resolves SRV DNS records.
|
47
|
+
def require_external_connectivity
|
48
|
+
before(:all) do
|
49
|
+
if ENV['EXTERNAL_DISABLED']
|
50
|
+
skip "Test requires external connectivity"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def require_mongo_kerberos
|
56
|
+
before(:all) do
|
57
|
+
# TODO Use a more generic environment variable name if/when
|
58
|
+
# Mongoid tests get Kerberos configurations.
|
59
|
+
unless %w(1 yes true).include?(ENV['MONGO_RUBY_DRIVER_KERBEROS']&.downcase)
|
60
|
+
skip 'Set MONGO_RUBY_DRIVER_KERBEROS=1 in environment to run Kerberos unit tests'
|
61
|
+
end
|
62
|
+
require 'mongo_kerberos'
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def require_linting
|
67
|
+
before(:all) do
|
68
|
+
unless Mongo::Lint.enabled?
|
69
|
+
skip "Linting is not enabled"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Some tests will fail if linting is enabled:
|
75
|
+
# 1. Tests that pass invalid options to client, etc. which the linter
|
76
|
+
# rejects.
|
77
|
+
# 2. Tests that set expectations on topologies, server descriptions, etc.
|
78
|
+
# (since setting expectations requires mutating said objects, and when
|
79
|
+
# linting is on those objects are frozen).
|
80
|
+
def require_no_linting
|
81
|
+
before(:all) do
|
82
|
+
if Mongo::Lint.enabled?
|
83
|
+
skip "Linting is enabled"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def require_libmongocrypt
|
89
|
+
before(:all) do
|
90
|
+
# If FLE is set in environment, the entire test run is supposed to
|
91
|
+
# include FLE therefore run the FLE tests.
|
92
|
+
if (ENV['LIBMONGOCRYPT_PATH'] || '').empty? && (ENV['FLE'] || '').empty?
|
93
|
+
skip 'Test requires path to libmongocrypt to be specified in LIBMONGOCRYPT_PATH env variable'
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def min_libmongocrypt_version(version)
|
99
|
+
require_libmongocrypt
|
100
|
+
before(:all) do
|
101
|
+
actual_version = Utils.parse_version(Mongo::Crypt::Binding.mongocrypt_version(nil))
|
102
|
+
min_version = Utils.parse_version(version)
|
103
|
+
unless actual_version >= min_version
|
104
|
+
skip "libmongocrypt version #{min_version} required, but version #{actual_version} is available"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def require_no_libmongocrypt
|
110
|
+
before(:all) do
|
111
|
+
if ENV['LIBMONGOCRYPT_PATH']
|
112
|
+
skip 'Test requires libmongocrypt to not be configured'
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def require_aws_auth
|
118
|
+
before(:all) do
|
119
|
+
unless (ENV['AUTH'] || '') =~ /^aws/
|
120
|
+
skip 'This test requires AUTH=aws* and an appropriately configured runtime environment'
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def require_ec2_host
|
126
|
+
before(:all) do
|
127
|
+
if $have_aws.nil?
|
128
|
+
$have_aws = begin
|
129
|
+
require 'open-uri'
|
130
|
+
begin
|
131
|
+
Timeout.timeout(3.81) do
|
132
|
+
URI.parse('http://169.254.169.254/latest/meta-data/profile').open.read
|
133
|
+
end
|
134
|
+
true
|
135
|
+
# When trying to use the EC2 metadata endpoint on ECS:
|
136
|
+
# Errno::EINVAL: Failed to open TCP connection to 169.254.169.254:80 (Invalid argument - connect(2) for "169.254.169.254" port 80)
|
137
|
+
rescue Timeout::Error, Errno::ETIMEDOUT, Errno::EINVAL, OpenURI::HTTPError => $aws_error
|
138
|
+
false
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
unless $have_aws
|
143
|
+
skip "EC2 instance metadata is not available - assuming not running on an EC2 instance: #{$aws_error.class}: #{$aws_error}"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def require_stress
|
149
|
+
before(:all) do
|
150
|
+
if !SpecConfig.instance.stress?
|
151
|
+
skip 'Set STRESS=1 in environment to run stress tests'
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def require_fork
|
157
|
+
before(:all) do
|
158
|
+
if !SpecConfig.instance.fork?
|
159
|
+
skip 'Set FORK=1 in environment to run fork tests'
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def require_ocsp
|
165
|
+
before(:all) do
|
166
|
+
if !SpecConfig.instance.ocsp?
|
167
|
+
skip 'Set OCSP=1 in environment to run OCSP tests'
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def require_ocsp_verifier
|
173
|
+
before(:all) do
|
174
|
+
if !SpecConfig.instance.ocsp_verifier?
|
175
|
+
skip 'Set OCSP_VERIFIER=1 in environment to run OCSP verifier tests'
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def require_ocsp_connectivity
|
181
|
+
before(:all) do
|
182
|
+
if !SpecConfig.instance.ocsp_connectivity?
|
183
|
+
skip 'Set OCSP_CONNECTIVITY=pass or OCSP_CONNECTIVITY=fail in environment to run OCSP connectivity tests'
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def require_active_support
|
189
|
+
before(:all) do
|
190
|
+
if !SpecConfig.instance.active_support?
|
191
|
+
skip 'This test requires ActiveSupport; set WITH_ACTIVE_SUPPORT=1 in environment'
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def no_active_support
|
197
|
+
before(:all) do
|
198
|
+
if SpecConfig.instance.active_support?
|
199
|
+
skip 'This test requires no ActiveSupport; unset WITH_ACTIVE_SUPPORT in environment'
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def require_fallbacks
|
205
|
+
before(:all) do
|
206
|
+
unless %w(yes true 1).include?((ENV['TEST_I18N_FALLBACKS'] || '').downcase)
|
207
|
+
skip 'Set TEST_I18N_FALLBACKS=1 environment variable to run these tests'
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def require_no_fallbacks
|
213
|
+
before(:all) do
|
214
|
+
if %w(yes true 1).include?((ENV['TEST_I18N_FALLBACKS'] || '').downcase)
|
215
|
+
skip 'Set TEST_I18N_FALLBACKS=0 environment variable to run these tests'
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
# This is a macro for retrying flaky tests on CI that occasionally fail.
|
221
|
+
# Note that the tests will only be retried on CI.
|
222
|
+
#
|
223
|
+
# @param [ Integer ] :tries The number of times to retry.
|
224
|
+
# @param [ Integer ] :sleep The number of seconds to sleep in between retries.
|
225
|
+
# If nothing, or nil, is passed, we won't wait in between retries.
|
226
|
+
def retry_test(tries: 3, sleep: nil)
|
227
|
+
if %w(1 yes true).include?(ENV['CI'])
|
228
|
+
around do |example|
|
229
|
+
if sleep
|
230
|
+
example.run_with_retry retry: tries, retry_wait: sleep
|
231
|
+
else
|
232
|
+
example.run_with_retry retry: tries
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
@@ -0,0 +1,284 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
require_relative 'product_data'
|
6
|
+
|
7
|
+
module Mrss
|
8
|
+
module Release
|
9
|
+
class Candidate
|
10
|
+
# Release note section titles, by pr type
|
11
|
+
SECTION_TITLE = {
|
12
|
+
bcbreak: "Breaking Changes",
|
13
|
+
feature: "New Features",
|
14
|
+
bug: "Bug Fixes",
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
# GitHub labels
|
18
|
+
BCBREAK = 'bcbreak'
|
19
|
+
FEATURE = 'feature'
|
20
|
+
BUG = 'bug'
|
21
|
+
PATCH = 'patch'
|
22
|
+
|
23
|
+
def self.instance
|
24
|
+
@instance ||= new
|
25
|
+
|
26
|
+
yield @instance if block_given?
|
27
|
+
|
28
|
+
@instance
|
29
|
+
end
|
30
|
+
|
31
|
+
def product
|
32
|
+
@product ||= ProductData.new
|
33
|
+
end
|
34
|
+
|
35
|
+
def bump_version
|
36
|
+
product.bump_version(release_type)
|
37
|
+
end
|
38
|
+
|
39
|
+
def bump_version!
|
40
|
+
product.bump_version!(release_type)
|
41
|
+
end
|
42
|
+
|
43
|
+
def branch_name
|
44
|
+
@branch_name ||= "rc-#{product.version}"
|
45
|
+
end
|
46
|
+
|
47
|
+
# return a string of commit names since the last release
|
48
|
+
def pending_changes
|
49
|
+
@changes ||= begin
|
50
|
+
range = product.tag_exists? ? "#{product.tag_name}.." : ""
|
51
|
+
`git log --pretty=format:"%s" #{range}`
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# return a list of PR numbers since the last release
|
56
|
+
def pending_pr_numbers
|
57
|
+
@pending_pr_numbers ||= pending_changes.
|
58
|
+
lines.
|
59
|
+
map { |line| line.match(/\(#(\d+)\)$/).then { |m| m && m[1] } }.
|
60
|
+
compact.
|
61
|
+
sort.reverse
|
62
|
+
end
|
63
|
+
|
64
|
+
# return a JSON string of PR data
|
65
|
+
def pending_pr_dump
|
66
|
+
@pending_pr_dump ||= `gh pr list --state all --limit 256 --json number,title,labels,url,body --jq 'map(select([.number] | inside([#{pending_pr_numbers.join(',')}]))) | sort_by(.number)'`
|
67
|
+
end
|
68
|
+
|
69
|
+
# return a list of PR data since the last release
|
70
|
+
def pending_prs
|
71
|
+
@pending_prs ||= JSON.parse(pending_pr_dump)
|
72
|
+
end
|
73
|
+
|
74
|
+
# return a list of pending prs with additional attributes (summary,
|
75
|
+
# short title, jira issue number).
|
76
|
+
def decorated_prs
|
77
|
+
@decorated_prs ||= pending_prs.map do |pr|
|
78
|
+
jira_issue, pr_title = split_pr_title(pr)
|
79
|
+
summary = extract_summary(pr)
|
80
|
+
type = pr_type(pr)
|
81
|
+
type_code = pr_type_code(type)
|
82
|
+
patch_flag = pr_patch_flag?(pr)
|
83
|
+
|
84
|
+
pr.merge('jira' => jira_issue,
|
85
|
+
'short-title' => pr_title,
|
86
|
+
'summary' => summary,
|
87
|
+
'type' => type,
|
88
|
+
'type-code' => type_code,
|
89
|
+
'patch' => patch_flag)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# return a hash of decorated prs grouped by :bcbreak, :feature, or :bug
|
94
|
+
def prs_by_type
|
95
|
+
@prs_by_type ||= decorated_prs.group_by { |pr| pr['type'] }
|
96
|
+
end
|
97
|
+
|
98
|
+
# returns 'major', 'minor', or 'patch', depending on the presence of
|
99
|
+
# (respectively) :bcbreak, :feature, or :bug labels.
|
100
|
+
#
|
101
|
+
# If the RELEASE environment variable is set, its value will be used
|
102
|
+
# directly, ignoring whatever PR labels might exist.
|
103
|
+
def release_type
|
104
|
+
@release_type ||= if ENV['RELEASE']
|
105
|
+
ENV['RELEASE']
|
106
|
+
elsif prs_by_type[:bcbreak]
|
107
|
+
'major'
|
108
|
+
elsif prs_by_type[:feature] && prs_by_type[:feature].any? { |pr| !pr['patch'] }
|
109
|
+
'minor'
|
110
|
+
else
|
111
|
+
'patch'
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# returns the generated release notes as a string
|
116
|
+
def release_notes
|
117
|
+
@release_notes ||= release_notes_intro +
|
118
|
+
%i[ bcbreak feature bug ].
|
119
|
+
flat_map { |type| release_notes_for_type(type) }.join("\n")
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
# returns an array of strings, each string representing a single line
|
125
|
+
# in the release notes for the PR's of the given type.
|
126
|
+
def release_notes_for_type(type)
|
127
|
+
return [] unless prs_by_type[type]
|
128
|
+
|
129
|
+
[].tap do |lines|
|
130
|
+
lines << "\# #{SECTION_TITLE[type]}"
|
131
|
+
lines << ''
|
132
|
+
|
133
|
+
prs = prs_by_type[type]
|
134
|
+
summarized, unsummarized = prs.partition { |pr| pr['summary'] }
|
135
|
+
|
136
|
+
summarized.each do |pr|
|
137
|
+
header = [ '### ' ]
|
138
|
+
header << "[#{pr['jira']}](#{jira_url(pr['jira'])}) " if pr['jira']
|
139
|
+
header << "#{pr['short-title']} ([PR](#{pr['url']}))"
|
140
|
+
lines << header.join
|
141
|
+
lines << ''
|
142
|
+
lines << pr['summary']
|
143
|
+
lines << ''
|
144
|
+
end
|
145
|
+
|
146
|
+
if summarized.any? && unsummarized.any?
|
147
|
+
lines << ''
|
148
|
+
lines << [ '### Other ', SECTION_TITLE[type] ].join
|
149
|
+
lines << ''
|
150
|
+
end
|
151
|
+
|
152
|
+
unsummarized.each do |pr|
|
153
|
+
line = [ '* ' ]
|
154
|
+
line << "[#{pr['jira']}](#{jira_url(pr['jira'])}) " if pr['jira']
|
155
|
+
line << "#{pr['short-title']} ([PR](#{pr['url']}))"
|
156
|
+
|
157
|
+
lines << line.join
|
158
|
+
end
|
159
|
+
|
160
|
+
lines << ''
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# returns the URL of for the given jira issue
|
165
|
+
def jira_url(issue)
|
166
|
+
"https://jira.mongodb.org/browse/#{issue}"
|
167
|
+
end
|
168
|
+
|
169
|
+
# assumes a pr title in the format of "JIRA-1234 PR Title (#1234)",
|
170
|
+
# returns a tuple of [ jira-issue, title ], where jira-issue may be
|
171
|
+
# blank (if no jira issue is in the title).
|
172
|
+
def split_pr_title(pr)
|
173
|
+
title = pr['title'].gsub(/\(#\d+\)/, '').strip
|
174
|
+
|
175
|
+
if title =~ /^(\w+-\d+) (.*)$/
|
176
|
+
[ $1, $2 ]
|
177
|
+
else
|
178
|
+
[ nil, title ]
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# extracts the summary section from the pr and returns it (or returns nil
|
183
|
+
# if no summary section is detected)
|
184
|
+
def extract_summary(pr)
|
185
|
+
summary = []
|
186
|
+
accumulating = false
|
187
|
+
level = nil
|
188
|
+
|
189
|
+
pr['body'].lines.each do |line|
|
190
|
+
# a header of any level titled "summary" will begin the summary
|
191
|
+
if !accumulating && line =~ /^(\#+)\s+summary\s+$/i
|
192
|
+
accumulating = true
|
193
|
+
level = $1.length
|
194
|
+
|
195
|
+
# a header of any level less than or equal to the summary header's
|
196
|
+
# level will end the summary
|
197
|
+
elsif accumulating && line =~ /^\#{1,#{level}}\s+/
|
198
|
+
break
|
199
|
+
|
200
|
+
# otherwise, the line is part of the summary
|
201
|
+
elsif accumulating
|
202
|
+
summary << line
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
summary.any? ? summary.join.strip : nil
|
207
|
+
end
|
208
|
+
|
209
|
+
# Returns a symbol (:bcbreak, :feature, or :bug) that identifies the
|
210
|
+
# type of this PR that would most strongly influence what type of release
|
211
|
+
# it requires.
|
212
|
+
def pr_type(pr)
|
213
|
+
if pr['labels'].any? { |l| l['name'] == BCBREAK }
|
214
|
+
:bcbreak
|
215
|
+
elsif pr['labels'].any? { |l| l['name'] == FEATURE }
|
216
|
+
:feature
|
217
|
+
elsif pr['labels'].any? { |l| l['name'] == BUG }
|
218
|
+
:bug
|
219
|
+
else
|
220
|
+
nil
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# `true` if the `patch` label is applied to the PR. This is used to
|
225
|
+
# indicate that a "feature" PR should be treated as a patch, for
|
226
|
+
# determining the release type only.
|
227
|
+
def pr_patch_flag?(pr)
|
228
|
+
pr['labels'].any? { |l| l['name'] == PATCH }
|
229
|
+
end
|
230
|
+
|
231
|
+
def pr_type_code(type)
|
232
|
+
case type
|
233
|
+
when :bcbreak then 'x'
|
234
|
+
when :feature then 'f'
|
235
|
+
when :bug then 'b'
|
236
|
+
else '?'
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def series
|
241
|
+
major, minor, = product.version_parts
|
242
|
+
|
243
|
+
case release_type
|
244
|
+
when 'minor' then
|
245
|
+
"#{major}.x"
|
246
|
+
when 'patch' then
|
247
|
+
"#{major}.#{minor}.x"
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
# Return a string containing the markdown-formatted intro block for
|
252
|
+
# the release notes of this candidate.
|
253
|
+
def release_notes_intro
|
254
|
+
release_description = case release_type
|
255
|
+
when 'major' then 'major release'
|
256
|
+
when 'minor' then "minor release in the #{series} series"
|
257
|
+
when 'patch' then "patch release in the #{series} series"
|
258
|
+
end
|
259
|
+
|
260
|
+
<<~INTRO
|
261
|
+
The MongoDB Ruby team is pleased to announce version #{product.version}
|
262
|
+
of the `#{product.package}` gem - #{product.description}.
|
263
|
+
This is a new #{release_description} of #{product.name}.
|
264
|
+
|
265
|
+
Install this release using [RubyGems](https://rubygems.org/) via the command line as follows:
|
266
|
+
|
267
|
+
~~~
|
268
|
+
gem install -v #{product.version} #{product.package}
|
269
|
+
~~~
|
270
|
+
|
271
|
+
Or simply add it to your `Gemfile`:
|
272
|
+
|
273
|
+
~~~
|
274
|
+
gem '#{product.package}', '#{product.version}'
|
275
|
+
~~~
|
276
|
+
|
277
|
+
Have any feedback? Click on through to MongoDB's JIRA and
|
278
|
+
[open a new ticket](#{product.jira_project_url}) to let us know what's on your mind 🧠.
|
279
|
+
|
280
|
+
INTRO
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
module Mrss
|
6
|
+
module Release
|
7
|
+
class ProductData
|
8
|
+
FILE_PATH = 'product.yml'
|
9
|
+
|
10
|
+
def self.init!
|
11
|
+
if File.exist?(FILE_PATH)
|
12
|
+
raise "#{FILE_PATH} already exists; refusing to overwrite it"
|
13
|
+
end
|
14
|
+
|
15
|
+
initial_data = {
|
16
|
+
'name' => 'Product Name',
|
17
|
+
'description' => 'a very short description of the product',
|
18
|
+
'package' => 'product_package',
|
19
|
+
'jira' => 'https://url.to.jira/project',
|
20
|
+
'version' => { 'number' => '1.0.0',
|
21
|
+
'file' => 'path/to/version.rb' }
|
22
|
+
}
|
23
|
+
|
24
|
+
File.write(FILE_PATH, initial_data.to_yaml)
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize
|
28
|
+
@hash = YAML.load_file(FILE_PATH)
|
29
|
+
end
|
30
|
+
|
31
|
+
def save_product_file!
|
32
|
+
File.write(FILE_PATH, @hash.to_yaml)
|
33
|
+
end
|
34
|
+
|
35
|
+
def rewrite_version_file!
|
36
|
+
version_module = File.read(version_file)
|
37
|
+
new_module = version_module.
|
38
|
+
sub(/^(\s*)(VERSION\s*=\s*).*$/) { "#{$1}#{$2}#{quoted_version}" }
|
39
|
+
File.write(version_file, new_module)
|
40
|
+
end
|
41
|
+
|
42
|
+
def version
|
43
|
+
@hash['version']['number']
|
44
|
+
end
|
45
|
+
|
46
|
+
def quoted_version
|
47
|
+
if version.include?("'")
|
48
|
+
version.inspect
|
49
|
+
else
|
50
|
+
"'#{version}'"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def version=(number)
|
55
|
+
@hash['version']['number'] = number
|
56
|
+
end
|
57
|
+
|
58
|
+
# returns an array of [ major, minor, patch, suffix ].
|
59
|
+
#
|
60
|
+
# each element will be returned as a String.
|
61
|
+
def version_parts
|
62
|
+
version.split(/\./, 4)
|
63
|
+
end
|
64
|
+
|
65
|
+
# bump the version according to the given release type:
|
66
|
+
#
|
67
|
+
# 'major' -> increment major component, zero the others
|
68
|
+
# 'minor' -> increment minor component, zero the patch
|
69
|
+
# 'patch' -> increment the patch component
|
70
|
+
def bump_version(release)
|
71
|
+
major, minor, patch, suffix = version_parts
|
72
|
+
|
73
|
+
case release
|
74
|
+
when 'major' then
|
75
|
+
major = major.to_i + 1
|
76
|
+
minor = patch = 0
|
77
|
+
when 'minor'
|
78
|
+
minor = minor.to_i + 1
|
79
|
+
patch = 0
|
80
|
+
when 'patch'
|
81
|
+
patch = patch.to_i + 1
|
82
|
+
else
|
83
|
+
raise ArgumentError, "invalid release type: #{release.inspect}"
|
84
|
+
end
|
85
|
+
|
86
|
+
self.version = [ major, minor, patch ].join('.')
|
87
|
+
end
|
88
|
+
|
89
|
+
# Invokes `#bump_version`, and then saves the new version to the
|
90
|
+
# product.yml file and to the version.rb file.
|
91
|
+
def bump_version!(release)
|
92
|
+
bump_version(release)
|
93
|
+
save_product_file!
|
94
|
+
rewrite_version_file!
|
95
|
+
end
|
96
|
+
|
97
|
+
def version_file
|
98
|
+
@hash['version']['file']
|
99
|
+
end
|
100
|
+
|
101
|
+
def name
|
102
|
+
@hash['name']
|
103
|
+
end
|
104
|
+
|
105
|
+
# The description is intended to be used in places where it can be
|
106
|
+
# appended to the end of a sentence, e.g.
|
107
|
+
#
|
108
|
+
# "We just released #{product.name} - #{product.description}!"
|
109
|
+
#
|
110
|
+
# Markdown formatting is allowed (even expected).
|
111
|
+
def description
|
112
|
+
@hash['description']
|
113
|
+
end
|
114
|
+
|
115
|
+
def package
|
116
|
+
@hash['package']
|
117
|
+
end
|
118
|
+
|
119
|
+
def jira_project_url
|
120
|
+
@hash['jira']
|
121
|
+
end
|
122
|
+
|
123
|
+
def tag_name
|
124
|
+
"v#{version}"
|
125
|
+
end
|
126
|
+
|
127
|
+
def tag_exists?(tag = tag_name)
|
128
|
+
`git tag -l #{tag}`.strip == tag
|
129
|
+
end
|
130
|
+
|
131
|
+
def branch_exists?(branch)
|
132
|
+
`git branch -l #{branch}`.strip == branch
|
133
|
+
end
|
134
|
+
|
135
|
+
def base_branch
|
136
|
+
@base_branch ||= begin
|
137
|
+
major, minor, = version_parts
|
138
|
+
branch = "#{major}.#{minor}-stable"
|
139
|
+
branch_exists?(branch) ? branch : 'master'
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|