0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-06 02:51:10 +08:00
discourse/spec/tasks/release_spec.rb
David Taylor 0f1c9574f2
DEV: Update stage_security_fixes rake task (#37346)
- Moves it from `version_bump.rake` to the new `release.rake`. This is
the last thing which was pending upgrade, so we can now delete the whole
`version_bump.rake` file & spec

- Adds support for release/* branches

- Adds an interactive prompt to choose which security PRs to include

- Creates & merges the PR using `gh` CLI. Less manual work.
2026-01-28 15:12:29 +00:00

506 lines
18 KiB
Ruby
Vendored

# frozen_string_literal: true
def run(*args)
out, err, status = Open3.capture3(*args)
raise "Command failed: #{args.inspect}\n#{out}\n#{err}" unless status.success?
out
end
def fake_version_rb(version)
File.read("#{Rails.root}/lib/version.rb").sub(/STRING = ".*"/, "STRING = \"#{version}\"")
end
RSpec.describe "tasks/version_bump" do
let(:tmpdir) { Dir.mktmpdir }
let(:origin_path) { "#{tmpdir}/origin-repo" }
let(:local_path) { "#{tmpdir}/local-repo" }
def update_versions_json(overrides)
Dir.chdir(origin_path) do
run "git", "checkout", "main"
current = JSON.parse(File.read("versions.json"))
File.write("versions.json", JSON.pretty_generate(current.merge(overrides)))
run "git", "add", "versions.json"
run "git", "commit", "-m", "Update versions.json"
end
end
before do
ENV["RUNNING_RELEASE_IN_RSPEC_TESTS"] = "1"
Rake::Task.tasks.each { |t| t.reenable }
FileUtils.mkdir_p origin_path
Dir.chdir(origin_path) do
FileUtils.mkdir_p "lib"
FileUtils.mkdir_p "tmp"
File.write(".gitignore", "tmp\n")
File.write("lib/version.rb", fake_version_rb("2025.12.0-latest"))
File.write(
"versions.json",
JSON.pretty_generate(
{
"2025.1" => {
"released" => true,
"esr" => true,
},
"2025.2" => {
"released" => true,
"esr" => false,
},
"2025.3" => {
"released" => true,
"esr" => false,
},
"2025.4" => {
"released" => true,
"esr" => false,
},
"2025.5" => {
"released" => true,
"esr" => false,
},
"2025.6" => {
"released" => true,
"esr" => false,
},
"2025.7" => {
"released" => false,
"esr" => true,
},
"2025.8" => {
"released" => false,
"esr" => false,
},
"2025.9" => {
"released" => false,
"esr" => false,
},
"2025.10" => {
"released" => false,
"esr" => false,
},
"2025.11" => {
"released" => false,
"esr" => false,
},
"2025.12" => {
"released" => false,
"esr" => false,
},
},
),
)
run "git", "init"
run "git", "checkout", "-b", "main"
run "git", "add", "."
run "git", "commit", "-m", "Initial commit"
run "git", "checkout", "-b", "stable"
File.write("#{origin_path}/lib/version.rb", fake_version_rb("3.1.2"))
run "git", "add", "."
run "git", "commit", "-m", "Previous stable version bump"
run "git", "checkout", "main"
run "git", "config", "receive.denyCurrentBranch", "ignore"
end
run "git", "clone", "-b", "main", origin_path, local_path
end
after do
FileUtils.remove_entry(tmpdir)
ENV.delete("RUNNING_VERSION_BUMP_IN_RSPEC_TESTS")
end
describe "release:maybe_tag_release" do
it "does not tag if commit is not on a release/* branch" do
Dir.chdir(local_path) do
run "git", "switch", "-c", "some-other-branch"
File.write("lib/version.rb", fake_version_rb("2025.6.0"))
run "git", "add", "."
run "git", "commit", "-m", "release 2025.6.0"
commit_hash = run("git", "rev-parse", "HEAD").strip
# Not on a release/* branch, should not tag
capture_stdout { invoke_rake_task("release:maybe_tag_release", commit_hash) }
# Should not tag, so v2025.6.0 should not exist
expect(run("git", "tag").lines.map(&:strip)).not_to include("v2025.6.0")
end
end
it "tags a release if tag does not exist" do
Dir.chdir(local_path) do
File.write("lib/version.rb", fake_version_rb("2025.3.0"))
run "git", "add", "."
run "git", "commit", "-m", "release 2025.3.0"
commit_hash = run("git", "rev-parse", "HEAD").strip
run "git", "branch", "release/2025.3"
output = capture_stdout { invoke_rake_task("release:maybe_tag_release", commit_hash) }
expect(output).to include("Tagging release v2025.3.0")
expect(run("git", "tag").lines.map(&:strip)).to include("v2025.3.0")
end
end
it "tags first commit of a pre-release cycle" do
Dir.chdir(local_path) do
File.write("lib/version.rb", fake_version_rb("2025.4.0-latest"))
run "git", "add", "."
run "git", "commit", "-m", "bump to 2025.4.0-latest"
commit_hash = run("git", "rev-parse", "HEAD").strip
output = capture_stdout { invoke_rake_task("release:maybe_tag_release", commit_hash) }
expect(output).to include("Tagging release v2025.4.0-latest")
expect(run("git", "tag").lines.map(&:strip)).to include("v2025.4.0-latest")
end
end
it "skips tagging if tag already exists" do
Dir.chdir(local_path) do
File.write("lib/version.rb", fake_version_rb("2025.5.0"))
run "git", "add", "."
run "git", "commit", "-m", "release 2025.5.0"
commit_hash = run("git", "rev-parse", "HEAD").strip
run "git", "branch", "release/2025.5"
# Create tag manually
run "git", "tag", "-a", "v2025.5.0", "-m", "version 2025.5.0"
output = capture_stdout { invoke_rake_task("release:maybe_tag_release", commit_hash) }
expect(output).to include("Tag v2025.5.0 already exists, skipping")
end
end
end
describe "release:update_release_tags" do
it "creates release alias tags when version matches latest release" do
Dir.chdir(local_path) do
File.write("lib/version.rb", fake_version_rb("2025.6.0"))
run "git", "add", "."
run "git", "commit", "-m", "release 2025.6.0"
commit_hash = run("git", "rev-parse", "HEAD").strip
capture_stdout { invoke_rake_task("release:update_release_tags", commit_hash) }
tags = run("git", "tag").lines.map(&:strip)
ReleaseUtils::RELEASE_TAGS.each { |tag| expect(tags).to include(tag) }
end
end
it "skips release tags when version is older than latest release" do
Dir.chdir(local_path) do
File.write("lib/version.rb", fake_version_rb("2025.1.0"))
run "git", "add", "."
run "git", "commit", "-m", "old release"
commit_hash = run("git", "rev-parse", "HEAD").strip
output = capture_stdout { invoke_rake_task("release:update_release_tags", commit_hash) }
expect(output).to include("older than latest release")
tags = run("git", "tag").lines.map(&:strip)
ReleaseUtils::RELEASE_TAGS.each { |tag| expect(tags).not_to include(tag) }
end
end
it "ignores unreleased versions when determining latest release" do
Dir.chdir(local_path) do
File.write("lib/version.rb", fake_version_rb("2025.6.0"))
run "git", "add", "."
run "git", "commit", "-m", "release 2025.6.0"
commit_hash = run("git", "rev-parse", "HEAD").strip
capture_stdout { invoke_rake_task("release:update_release_tags", commit_hash) }
tags = run("git", "tag").lines.map(&:strip)
ReleaseUtils::RELEASE_TAGS.each { |tag| expect(tags).to include(tag) }
end
end
it "does not create ESR tags for non-ESR releases" do
Dir.chdir(local_path) do
File.write("lib/version.rb", fake_version_rb("2025.6.0"))
run "git", "add", "."
run "git", "commit", "-m", "release 2025.6.0"
commit_hash = run("git", "rev-parse", "HEAD").strip
capture_stdout { invoke_rake_task("release:update_release_tags", commit_hash) }
tags = run("git", "tag").lines.map(&:strip)
ReleaseUtils::ESR_TAGS.each { |tag| expect(tags).not_to include(tag) }
end
end
it "creates ESR alias tags when version is in the latest released ESR series" do
Dir.chdir(local_path) do
update_versions_json({ "2025.7" => { "released" => true, "esr" => true } })
File.write("lib/version.rb", fake_version_rb("2025.7.1"))
run "git", "add", "."
run "git", "commit", "-m", "release 2025.7.1"
commit_hash = run("git", "rev-parse", "HEAD").strip
capture_stdout { invoke_rake_task("release:update_release_tags", commit_hash) }
tags = run("git", "tag").lines.map(&:strip)
ReleaseUtils::RELEASE_TAGS.each { |tag| expect(tags).to include(tag) }
ReleaseUtils::ESR_TAGS.each { |tag| expect(tags).to include(tag) }
end
end
it "does not add ESR tags to non-ESR releases newer than the latest ESR" do
Dir.chdir(local_path) do
update_versions_json(
{
"2025.7" => {
"released" => true,
"esr" => true,
},
"2025.8" => {
"released" => true,
"esr" => false,
},
},
)
File.write("lib/version.rb", fake_version_rb("2025.8.0"))
run "git", "add", "."
run "git", "commit", "-m", "release 2025.8.0"
commit_hash = run("git", "rev-parse", "HEAD").strip
capture_stdout { invoke_rake_task("release:update_release_tags", commit_hash) }
tags = run("git", "tag").lines.map(&:strip)
ReleaseUtils::RELEASE_TAGS.each { |tag| expect(tags).to include(tag) }
ReleaseUtils::ESR_TAGS.each { |tag| expect(tags).not_to include(tag) }
end
end
end
it "can create a new release branch" do
latest_hash, previous_hash = nil
Dir.chdir(local_path) do
File.write("lib/version.rb", fake_version_rb("2025.1.0-latest"))
run "git", "add", "."
run "git", "commit", "-m", "developing 2025.1"
previous_hash = run("git", "rev-parse", "HEAD").strip
File.write("lib/version.rb", fake_version_rb("2025.2.0-latest"))
run "git", "add", "."
run "git", "commit", "-m", "begin development of 2025.2-latest"
latest_hash = run("git", "rev-parse", "HEAD").strip
output = capture_stdout { invoke_rake_task("release:maybe_cut_branch", latest_hash) }
expect(output).to include("Created new branch")
end
Dir.chdir(origin_path) do
run "git", "checkout", "release/2025.1"
branch_tip = run("git", "rev-parse", "HEAD").strip
expect(branch_tip).to eq(previous_hash)
end
end
describe "release:prepare_next_version" do
it "bumps version to current month format when current version is older" do
Dir.chdir(local_path) do
File.write("lib/version.rb", fake_version_rb("2024.1.0-latest"))
run "git", "add", "."
run "git", "commit", "-m", "old version"
run "git", "push", "origin", "main"
freeze_time Time.utc(2025, 9, 15) do
capture_stdout { invoke_rake_task("release:prepare_next_version") }
end
end
Dir.chdir(origin_path) do
run "git", "reset", "--hard"
run "git", "checkout", "version-bump/main"
version_rb_content = File.read("lib/version.rb")
expect(version_rb_content).to include('STRING = "2025.9.0-latest"')
end
end
it "increments minor version when current version is already >= target month version" do
Dir.chdir(local_path) do
File.write("lib/version.rb", fake_version_rb("2025.10.0-latest"))
run "git", "add", "."
run "git", "commit", "-m", "current month version"
run "git", "push", "origin", "main"
freeze_time Time.utc(2025, 10, 15) do
output = capture_stdout { invoke_rake_task("release:prepare_next_version") }
expect(output).to include("is already >= 2025.10.0-latest. Incrementing instead.")
end
end
Dir.chdir(origin_path) do
run "git", "reset", "--hard"
run "git", "checkout", "version-bump/main"
version_rb_content = File.read("lib/version.rb")
expect(version_rb_content).to include('STRING = "2025.11.0-latest"')
end
end
it "rolls over to next year when incrementing past December" do
Dir.chdir(local_path) do
freeze_time Time.utc(2025, 12, 15) do
capture_stdout { invoke_rake_task("release:prepare_next_version") }
end
end
Dir.chdir(origin_path) do
run "git", "reset", "--hard"
run "git", "checkout", "version-bump/main"
version_rb_content = File.read("lib/version.rb")
expect(version_rb_content).to include('STRING = "2026.1.0-latest"')
end
end
it "updates versions.json with new version info" do
Dir.chdir(local_path) do
freeze_time Time.utc(2025, 12, 28) do
capture_stdout { invoke_rake_task("release:prepare_next_version") }
end
end
Dir.chdir(origin_path) do
run "git", "reset", "--hard"
run "git", "checkout", "version-bump/main"
version_rb_content = File.read("lib/version.rb")
expect(version_rb_content).to include('STRING = "2026.1.0-latest"')
versions_json = JSON.parse(File.read("versions.json"))
expect(versions_json["2026.1"]).to eq(
{
"developmentStartDate" => "2025-12-28",
"releaseDate" => "2026-01",
"supportEndDate" => "2026-09",
"released" => false,
"esr" => true,
"supported" => true,
},
)
expect(versions_json["2025.12"]["released"]).to eq(true)
expect(versions_json["2025.12"]["releaseDate"]).to eq("2025-12-28")
end
end
it "creates version-bump/main branch with proper commit message and PR" do
allow(ReleaseUtils).to receive(:gh).with("pr", "create", any_args).and_return(true)
Dir.chdir(local_path) do
File.write("lib/version.rb", fake_version_rb("2025.5.0-latest"))
run "git", "add", "."
run "git", "commit", "-m", "previous version"
run "git", "push", "origin", "main"
freeze_time Time.utc(2025, 10, 15) do
capture_stdout { invoke_rake_task("release:prepare_next_version") }
end
end
Dir.chdir(origin_path) do
run "git", "reset", "--hard"
run "git", "checkout", "version-bump/main"
current_branch = run("git", "branch", "--show-current").strip
expect(current_branch).to eq("version-bump/main")
commit_message = run("git", "log", "-1", "--pretty=%B").strip
expect(commit_message).to include("Begin development of v2025.10.0-latest")
expect(commit_message).to include(
"Merging this will trigger the creation of a `release/2025.5` branch on the preceding commit.",
)
end
expect(ReleaseUtils).to have_received(:gh).with(
"pr",
"create",
"--base",
"main",
"--head",
"version-bump/main",
"--title",
"DEV: Begin development of v2025.10.0-latest",
"--body",
a_string_including("Merging this will trigger the creation of a `release/2025.5` branch"),
"--label",
ReleaseUtils::PR_LABEL,
)
end
it "falls back to editing PR if create fails" do
allow(ReleaseUtils).to receive(:gh).with("pr", "create", any_args).and_return(false)
allow(ReleaseUtils).to receive(:gh).with("pr", "edit", any_args).and_return(true)
Dir.chdir(local_path) do
File.write("lib/version.rb", fake_version_rb("2025.5.0-latest"))
run "git", "add", "."
run "git", "commit", "-m", "previous version"
run "git", "push", "origin", "main"
freeze_time Time.utc(2025, 10, 15) do
capture_stdout { invoke_rake_task("release:prepare_next_version") }
end
end
expect(ReleaseUtils).to have_received(:gh).with(
"pr",
"edit",
"version-bump/main",
"--title",
"DEV: Begin development of v2025.10.0-latest",
"--body",
a_string_including("Merging this will trigger the creation of a `release/2025.5` branch"),
"--add-label",
ReleaseUtils::PR_LABEL,
)
end
end
describe "release:stage_security_fixes" do
it "stages a PR of multiple security fixes" do
Dir.chdir(origin_path) do
run "git", "checkout", "-b", "security-fix-one"
File.write("firstfile.txt", "contents")
run "git", "add", "firstfile.txt"
run "git", "-c", "commit.gpgsign=false", "commit", "-m", "security fix one, commit one"
File.write("secondfile.txt", "contents")
run "git", "add", "secondfile.txt"
run "git", "-c", "commit.gpgsign=false", "commit", "-m", "security fix one, commit two"
run "git", "checkout", "main"
run "git", "checkout", "-b", "security-fix-two"
File.write("somefile.txt", "contents")
run "git", "add", "somefile.txt"
run "git", "-c", "commit.gpgsign=false", "commit", "-m", "security fix two"
end
Dir.chdir(local_path) do
capture_stdout do
ENV["SECURITY_FIX_REFS"] = "origin/security-fix-one,origin/security-fix-two"
invoke_rake_task("release:stage_security_fixes", "main")
ensure
ENV.delete("SECURITY_FIX_REFS")
end
end
Dir.chdir(origin_path) do
expect(run("git", "log", "--pretty=%s", "main").lines.map(&:strip)).to eq(
[
"security fix two",
"security fix one, commit two",
"security fix one, commit one",
"Initial commit",
],
)
expect(run("git", "show", "main:somefile.txt")).to eq("contents")
expect(run("git", "show", "main:firstfile.txt")).to eq("contents")
expect(run("git", "show", "main:secondfile.txt")).to eq("contents")
end
end
end
end