diff --git a/.gitignore b/.gitignore index 9106b2a..5d76322 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ /pkg/ /spec/reports/ /tmp/ +.rspec-failures diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..c99d2e7 --- /dev/null +++ b/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/lib/decidim/maintainers_toolbox/changelog_generator.rb b/lib/decidim/maintainers_toolbox/changelog_generator.rb index b8a42b8..2caf023 100644 --- a/lib/decidim/maintainers_toolbox/changelog_generator.rb +++ b/lib/decidim/maintainers_toolbox/changelog_generator.rb @@ -61,6 +61,9 @@ def call if type_prs.any? type_prs.each do |_pr_title, data| + # Prevent throttling by GitHub REST API + sleep 1 + process_single_pr(data, type_data) end else diff --git a/lib/decidim/maintainers_toolbox/release_candidate_version.rb b/lib/decidim/maintainers_toolbox/release_candidate_version.rb new file mode 100644 index 0000000..94be899 --- /dev/null +++ b/lib/decidim/maintainers_toolbox/release_candidate_version.rb @@ -0,0 +1,320 @@ +# frozen_string_literal: true + +require_relative "changelog_generator" +require_relative "releaser_utils" +require_relative "github_manager/poster" + +module Decidim + module MaintainersToolbox + # Creates a release candidate version for this branch + # + # If we are in develop (i.e. v0.30.0.dev) it will create the rc1 (i.e. v0.30.0.rc1) + # If we are in the stable release with an rc (i.e. v0.30.0.rc1) it will create the next rc (i.e. v0.30.0.rc2) + # If we are in the stable release with a patch (i.e. v0.30.0) it will bail out + class ReleaseCandidateVersion + include Decidim::MaintainersToolbox::ReleaserUtils + + # @param token [String] token for GitHub authentication + # @param working_dir [String] current working directory. Useful for testing purposes + def initialize(token:, working_dir: Dir.pwd) + @token = token + @working_dir = working_dir + @old_version_number = old_version_number + end + + def call + check_branch_and_version_sanity + + prepare_next_development_version + prepare_next_release_candidate_version + end + + # The version number for the release that we are preparing + # + # @return [String] the version number + def version_number + @version_number ||= next_version_number_for_release_candidate(@old_version_number) + end + + private + + # Returns the next branch that needs to be created for the RC. + # + # @return [String] + def release_branch + @release_branch ||= begin + major, minor, _patch = parsed_version_number(version_number) + + "release/#{major}.#{minor}-stable" + end + end + + def check_branch_and_version_sanity + return if develop_branch? && dev_version_number? + return if release_branch? && rc_version_number? + + error_message = <<-EOERROR + Check if the branch is valid for a release candidate. It should be: + - develop with a dev version (i.e. develop branch with 0.30.0.dev decidim version) + - stable branch with a release candidate version (i.e. release/0.30-stable branch with 0.30.rc1 decidim version) + EOERROR + exit_with_errors(error_message) + end + + def develop_branch? + branch == "develop" + end + + def dev_version_number? + @old_version_number.match? /dev$/ + end + + def release_branch? + branch.start_with?("release/") + end + + def rc_version_number? + @old_version_number.match? /rc.$/ + end + + def prepare_next_development_version + return unless develop_branch? || dev_version_number? + + run("git pull origin develop") + + run("git checkout -b #{release_branch}") + run("git push origin #{release_branch}") + + puts "*" * 80 + puts "Create the stable branch in Crowdin and exit this shell to continue the release process" + puts "https://docs.decidim.org/en/develop/develop/maintainers/releases.html#_create_the_stable_branch_in_crowdin" + puts "*" * 80 + system ENV.fetch("SHELL") + + run("git checkout develop") + + next_dev_version = next_version_number_for_dev(@old_version_number) + prepare_branch = "chore/prepare/#{next_dev_version}" + run("git checkout -b #{prepare_branch}") + + bump_decidim_version(next_version_number_for_dev(@old_version_number)) + + run("bin/rake update_versions") + run("bin/rake patch_generators") + run("bin/rake bundle") + run("npm install") + run("bin/rake webpack") if Dir.exist?("decidim_app-design") + + generate_empty_changelog + generate_empty_release_notes + + run("git add .") + run("git commit -m 'Bump develop to next release version'") + run("git push origin #{prepare_branch}") + + create_develop_pull_request(prepare_branch, next_dev_version) + end + + def prepare_next_release_candidate_version + run("git checkout #{release_branch}") + run("git checkout -b chore/prepare/#{version_number}") + + bump_decidim_version(version_number) + + run("bin/rake update_versions") + run("bin/rake patch_generators") + run("bin/rake bundle") + + run("npm install") + run("bin/rake webpack") if Dir.exist?("decidim_app-design") + + check_tests + + generate_changelog + + run("git add .") + run("git commit -m 'Bump to #{version_number} version'") + run("git push origin chore/prepare/#{version_number}") + + create_pull_request + + finish_message = <<~EOMESSAGE + Finished the release process + Next steps: + + 1. Wait for the tests to finish and check that everything is passing before releasing the version. + NOTE: When you bump the version, the generator tests will fail because the gems and NPM packages + have not been actually published yet (as in sent to rubygems/npm). You may see errors such as + No matching version found for @decidim/browserslist-config@~0.xx.y in the CI logs. This should + be fine as long as you have ensured that the generators tests passed in the previous commit. + 2. Review and merge this PR + 3. Once that PR is merged, run the following commands to create the tags and push the gems to RubyGems and the packages to NPM: + > git pull + > bin/rake release_all + 4. Usually, at this point, the release branch is deployed to Metadecidim during, at least, one week to validate the stability of the version. + EOMESSAGE + + puts "*" * 80 + puts finish_message + end + + # Given a version number, returns the next release candidate + # + # If the current version number is `dev`, then we return the `rc1` version + # If the current version number is `rc`, then we return the next `rc` version + # Else, it means is a `minor` or `patch` version. On those cases we raise an Exception, as releases candidates should + # be only done from a `dev` or a `rc` version. + # + # @raise [InvalidVersionTypeError] + # + # @param current_version_number [String] - The version number of the current version + # + # @return [String] - the new version number + def next_version_number_for_release_candidate(current_version_number) + if current_version_number.include? "dev" + major, minor, patch = parsed_version_number(current_version_number) + new_version_number = "#{major}.#{minor}.#{patch}.rc1" + elsif current_version_number.include? "rc" + new_rc_number = current_version_number.match(/rc(\d)/)[1].to_i + 1 + new_version_number = current_version_number.gsub(/rc\d/, "rc#{new_rc_number}") + else + error_message = <<-EOMESSAGE + Trying to do a release candidate version from patch release. Bailing out. + You need to do a release candidate from a `dev` or from another `rc` version + EOMESSAGE + raise InvalidVersionTypeError, error_message + end + + new_version_number + end + + def next_version_number_for_dev(current_version_number) + major, minor, patch = parsed_version_number(current_version_number) + + "#{major}.#{minor.to_i + 1}.#{patch}.dev" + end + + def generate_empty_changelog + major, minor, patch = parsed_version_number(@old_version_number) + + changelog_contents = <<-EOCHANGELOG +# Changelog + +## [Unreleased](https://github.com/decidim/decidim/tree/HEAD) + +Nothing. + +... + +## Previous versions + +Please check [#{major}.#{minor}-stable](https://github.com/decidim/decidim/blob/release/#{major}.#{minor}-stable/CHANGELOG.md) for previous changes. +EOCHANGELOG + + File.write("CHANGELOG.md", changelog_contents) + end + + def generate_empty_release_notes + release_notes_contents = <<-EORELEASE +# Release Notes + +## 1. Upgrade notes + +As usual, we recommend that you have a full backup, of the database, application code and static files. + +To update, follow these steps: + +### 1.1. Update your Gemfile + +```ruby +gem "decidim", github: "decidim/decidim" +gem "decidim-dev", github: "decidim/decidim" +``` + +### 1.2. Run these commands + +```console +bundle update decidim +bin/rails decidim:upgrade +bin/rails db:migrate +``` + +### 1.3. Follow the steps and commands detailed in these notes + +## 2. General notes + +## 3. One time actions + +These are one time actions that need to be done after the code is updated in the production database. + +### 3.1. [[TITLE OF THE ACTION]] + +You can read more about this change on PR [#XXXX](https://github.com/decidim/decidim/pull/XXXX). + +## 4. Scheduled tasks + +Implementers need to configure these changes it in your scheduler task system in the production server. We give the examples +with `crontab`, although alternatively you could use `whenever` gem or the scheduled jobs of your hosting provider. + +### 4.1. [[TITLE OF THE TASK]] + +```bash +4 0 * * * cd /home/user/decidim_application && RAILS_ENV=production bundle exec rails decidim:TASK +``` + +You can read more about this change on PR [#XXXX](https://github.com/decidim/decidim/pull/XXXX). + +## 5. Changes in APIs + +### 5.1. [[TITLE OF THE CHANGE]] + +In order to [[REASONING (e.g. improve the maintenance of the code base)]] we have changed... + +If you have used code as such: + +```ruby +# Explain the usage of the API as it was in the previous version +result = 1 + 1 if before +``` + +You need to change it to: + +```ruby +# Explain the usage of the API as it is in the new version +result = 1 + 1 if after + ``` +EORELEASE + + File.write("RELEASE_NOTES.md", release_notes_contents) + end + + # Creates the pull request for bumping the develop version + # + # @param head_branch [String] the branch that we want to merge to develop + # @param next_dev_version [String] the name of the next dev version (for instance 0.99.0.dev) + # + # @return [void] + def create_develop_pull_request(head_branch, next_dev_version) + base_branch = "develop" + + params = { + title: "Bump develop to next release version (#{next_dev_version})", + body: "#### :tophat: What? Why? + +This PR prepares the next develop version. + +#### Testing + +All the tests should pass + +:hearts: Thank you! + ", + labels: ["type: internal"], + head: head_branch, + base: base_branch + } + Decidim::MaintainersToolbox::GithubManager::Poster.new(token: @token, params: params).call + end + end + end +end diff --git a/lib/decidim/maintainers_toolbox/release_patch_version.rb b/lib/decidim/maintainers_toolbox/release_patch_version.rb new file mode 100644 index 0000000..755d8a3 --- /dev/null +++ b/lib/decidim/maintainers_toolbox/release_patch_version.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +require_relative "releaser_utils" + +module Decidim + module MaintainersToolbox + class ReleasePatchVersion + include Decidim::MaintainersToolbox::ReleaserUtils + + # @param token [String] token for GitHub authentication + # @param working_dir [String] current working directory. Useful for testing purposes + def initialize(token:, working_dir: Dir.pwd) + @token = token + @working_dir = working_dir + end + + def call + exit_unless_release_branch + + prepare_next_patch_version + end + + # The version number for the release that we are preparing + # + # @return [String] the version number + def version_number + @version_number ||= next_version_number_for_patch_release(old_version_number) + end + + private + + # Raise an error if the branch does not start with the preffix "release/" + # or returns the branch name + # + # @raise [InvalidBranchError] + # + # @return [String] + def release_branch + @release_branch ||= branch + end + + def exit_unless_release_branch + return if branch.start_with?("release/") + + error_message = <<-EOERROR + This is not a release branch, change to the release branch branch to run this script. + EOERROR + exit_with_errors(error_message) + end + + def prepare_next_patch_version + run("git checkout #{release_branch}") + run("git pull origin #{release_branch}") + + bump_decidim_version(version_number) + run("bin/rake update_versions") + + run("bin/rake patch_generators") + + run("bin/rake bundle") + run("npm install") + run("bin/rake webpack") if Dir.exist?("decidim_app-design") + + check_tests + + generate_changelog + + run("git checkout -b chore/prepare/#{version_number}") + run("git commit -a -m 'Prepare #{version_number} release'") + run("git push origin chore/prepare/#{version_number}") + + create_pull_request + end + + # Given a version number, returns the next patch release + # + # If the current version number is `dev`, then we raise an Exception, as you need to first do a release candidate. + # If the current version number is `rc`, then we return the `0` patch version + # Else, it means is a `patch` version, so we return the next patch version + # + # @raise [InvalidVersionTypeError] + # + # @param current_version_number [String] - The version number of the current version + # + # @return [String] - the new version number + def next_version_number_for_patch_release(current_version_number) + major, minor, patch = parsed_version_number(current_version_number) + + if current_version_number.include? "dev" + error_message = <<-EOMESSAGE + Trying to do a patch version from dev release. Bailing out. + You need to do first a release candidate. + EOMESSAGE + raise InvalidVersionTypeError, error_message + elsif current_version_number.include? "rc" + new_version_number = "#{major}.#{minor}.0" + else + new_version_number = "#{major}.#{minor}.#{patch.to_i + 1}" + end + + new_version_number + end + end + end +end diff --git a/lib/decidim/maintainers_toolbox/releaser.rb b/lib/decidim/maintainers_toolbox/releaser.rb index 5b10930..b94ba3c 100644 --- a/lib/decidim/maintainers_toolbox/releaser.rb +++ b/lib/decidim/maintainers_toolbox/releaser.rb @@ -1,20 +1,17 @@ # frozen_string_literal: true -require "open3" -require_relative "github_manager/poster" require_relative "github_manager/querier/by_query" -require_relative "changelog_generator" + +require_relative "releaser_utils" +require_relative "release_candidate_version" +require_relative "release_patch_version" module Decidim module MaintainersToolbox class Releaser class InvalidMetadataError < StandardError; end - class InvalidBranchError < StandardError; end - - class InvalidVersionTypeError < StandardError; end - - DECIDIM_VERSION_FILE = ".decidim-version" + include Decidim::MaintainersToolbox::ReleaserUtils # @param token [String] token for GitHub authentication # @param version_type [String] The kind of release that you want to prepare. Supported values: rc, minor, patch @@ -32,223 +29,32 @@ def call exit_if_unstaged_changes if @exit_with_unstaged_changes exit_if_pending_crowdin_pull_request - puts "Starting the release process for #{version_number} in 10 seconds" - sleep 10 - - run("git checkout #{release_branch}") - run("git pull origin #{release_branch}") - - bump_decidim_version - run("bin/rake update_versions") + case @version_type + when "rc" + release = ReleaseCandidateVersion.new(token: @token, working_dir: @working_dir) + when "patch" + release = ReleasePatchVersion.new(token: @token, working_dir: @working_dir) + else + raise InvalidVersionTypeError, "This is not a valid version type" + end - run("bin/rake patch_generators") - - run("bin/rake bundle") - run("npm install") - run("bin/rake webpack") if Dir.exists?("decidim_app-design") - - check_tests - - generate_changelog - - run("git checkout -b chore/prepare/#{version_number}") - run("git commit -a -m 'Prepare #{version_number} release'") - run("git push origin chore/prepare/#{version_number}") + puts "Starting the release process for #{release.version_number} in 10 seconds" + sleep 10 - create_pull_request + release.call end end private - # The git branch - # - # @return [String] - def branch - @branch ||= capture("git rev-parse --abbrev-ref HEAD")[0].strip - end - - # Raise an error if the branch does not start with the preffix "release/" - # or returns the branch name - # - # @raise [InvalidBranchError] - # - # @return [String] - def release_branch - raise InvalidBranchError, "This is not a release branch, aborting" unless branch.start_with?("release/") - - branch - end - - # Changes the decidim version in the file - # - # @return [void] - def bump_decidim_version - File.write(DECIDIM_VERSION_FILE, version_number) - end - - # The version number for the release that we are preparing - # - # @todo support the "minor" type version - # - # @return [String] the version number - def version_number - @version_number ||= case @version_type - when "rc" - next_version_number_for_release_candidate(old_version_number) - when "patch" - next_version_number_for_patch_release(old_version_number) - else - raise InvalidVersionTypeError, "This is not a supported version type" - end - end - - def parsed_version_number(version_number) - /(?\d+)\.(?\d+)\.(?\d+)/ =~ version_number - - [major.to_i, minor.to_i, patch.to_i] - end - - # Given a version number, returns the next release candidate - # - # If the current version number is `dev`, then we return the `rc1` version - # If the current version number is `rc`, then we return the next `rc` version - # Else, it means is a `minor` or `patch` version. On those cases we raise an Exception, as releases candidates should - # be only done from a `dev` or a `rc` version. - # - # @raise [InvalidVersionTypeError] - # - # @param current_version_number [String] - The version number of the current version - # - # @return [String] - the new version number - def next_version_number_for_release_candidate(current_version_number) - if current_version_number.include? "dev" - major, minor, patch = parsed_version_number(current_version_number) - new_version_number = "#{major}.#{minor}.#{patch}.rc1" - elsif current_version_number.include? "rc" - new_rc_number = current_version_number.match(/rc(\d)/)[1].to_i + 1 - new_version_number = current_version_number.gsub(/rc\d/, "rc#{new_rc_number}") - else - error_message = <<-EOMESSAGE - Trying to do a release candidate version from patch release. Bailing out. - You need to do a release candidate from a `dev` or from another `rc` version - EOMESSAGE - raise InvalidVersionTypeError, error_message - end - - new_version_number - end - - # Given a version number, returns the next patch release - # - # If the current version number is `dev`, then we raise an Exception, as you need to first do a release candidate. - # If the current version number is `rc`, then we return the `0` patch version - # Else, it means is a `patch` version, so we return the next patch version - # - # @raise [InvalidVersionTypeError] - # - # @param current_version_number [String] - The version number of the current version - # - # @return [String] - the new version number - def next_version_number_for_patch_release(current_version_number) - major, minor, patch = parsed_version_number(current_version_number) - - if current_version_number.include? "dev" - error_message = <<-EOMESSAGE - Trying to do a patch version from dev release. Bailing out. - You need to do first a release candidate. - EOMESSAGE - raise InvalidVersionTypeError, error_message - elsif current_version_number.include? "rc" - new_version_number = "#{major}.#{minor}.0" - else - new_version_number = "#{major}.#{minor}.#{patch.to_i + 1}" - end - - new_version_number - end - - # The version number from the file - # - # @return [String] the version number - def old_version_number - File.read(DECIDIM_VERSION_FILE).strip - end - - # Run the tests and if fails restore the changes using git and exit with an error - # - # @return [void] - def check_tests - puts "Running specs" - output, status = capture("bin/rspec", { "ENFORCED_LOCALES" => "en,ca,es", "SKIP_NORMALIZATION" => "true" }) - - unless status.success? - run("git restore .") - puts output - exit_with_errors("Tests execution failed. Fix the errors and run again.") - end - end - - # Generates the changelog taking into account the last time the version changed - # - # @return [void] - def generate_changelog - sha_version = capture("git log -n 1 --pretty=format:%h -- .decidim-version")[0] - ChangeLogGenerator.new(token: @token, since_sha: sha_version).call - temporary_changelog = File.read("./temporary_changelog.md") - legacy_changelog = File.read("./CHANGELOG.md") - version_changelog = "## [#{version_number}](https://github.com/decidim/decidim/tree/#{version_number})\n\n#{temporary_changelog}\n" - changelog = legacy_changelog.gsub("# Changelog\n\n", "# Changelog\n\n#{version_changelog}") - File.write("./CHANGELOG.md", changelog) - end - - # Creates the pull request for bumping the version - # - # @return [void] - def create_pull_request - base_branch = release_branch - head_branch = "chore/prepare/#{version_number}" - - params = { - title: "Bump to v#{version_number} version", - body: "#### :tophat: What? Why? - -This PR changes the version of the #{release_branch} branch, so we can publish the release once this is approved and merged. - -#### Testing - -All the tests should pass, except for some generators tests, that will fail because the gems and NPM packages have not -been actually published yet (as in sent to rubygems/npm). -You will see errors such as `No matching version found for @decidim/browserslist-config@~0.xx.y` in the CI logs. - -:hearts: Thank you! - ", - labels: ["type: internal"], - head: head_branch, - base: base_branch - } - Decidim::MaintainersToolbox::GithubManager::Poster.new(token: @token, params: params).call - end - - # Captures to output of a command - # - # @return [Array] The stdout and stderr of the command and its status (aka error code) - def capture(cmd, env: {}) - Open3.capture2e(env, cmd) - end - - # Runs a command - # - # @return [void] - def run(cmd, out: $stdout) - system(cmd, out: out) - end - # Check if there is any open pull request from Crowdin in GitHub # # @return [Boolean] - true if there is any open PR def pending_crowdin_pull_requests? - pull_requests = Decidim::MaintainersToolbox::GithubManager::Querier::ByQuery.new(token: @token, query: { title: "New Crowdin updates", creator: "decidim-bot" }).call + pull_requests = Decidim::MaintainersToolbox::GithubManager::Querier::ByQuery.new( + token: @token, + query: { title: "New Crowdin updates", creator: "decidim-bot" } + ).call pull_requests.any? end @@ -278,14 +84,6 @@ def exit_if_unstaged_changes EOERROR exit_with_errors(error_message) end - - # Exit the script execution with a message - # - # @return [void] - def exit_with_errors(message) - puts message - exit 1 - end end end end diff --git a/lib/decidim/maintainers_toolbox/releaser_utils.rb b/lib/decidim/maintainers_toolbox/releaser_utils.rb new file mode 100644 index 0000000..d74d69e --- /dev/null +++ b/lib/decidim/maintainers_toolbox/releaser_utils.rb @@ -0,0 +1,120 @@ +# frozen_string_literal: true + +require "open3" +require_relative "changelog_generator" +require_relative "github_manager/poster" + +module Decidim + module MaintainersToolbox + module ReleaserUtils + class InvalidVersionTypeError < StandardError; end + + DECIDIM_VERSION_FILE = ".decidim-version".freeze + + # Exit the script execution with a message + # + # @return [void] + def exit_with_errors(message) + puts message + exit 1 + end + + # Captures to output of a command + # + # @return [Array] The stdout and stderr of the command and its status (aka error code) + def capture(cmd, env: {}) + Open3.capture2e(env, cmd) + end + + # Runs a command + # + # @return [void] + def run(cmd, out: $stdout) + system(cmd, out: out, exception: true) + end + + # The git branch + # + # @return [String] + def branch + @branch ||= capture("git rev-parse --abbrev-ref HEAD")[0].strip + end + + # The version number from the file + # + # @return [String] the version number + def old_version_number + File.read(DECIDIM_VERSION_FILE).strip + end + + def parsed_version_number(version_number) + /(?\d+)\.(?\d+)\.(?\d+)/ =~ version_number + + [major.to_i, minor.to_i, patch.to_i] + end + + # Run the tests and if fails restore the changes using git and exit with an error + # + # @return [void] + def check_tests + puts "Running specs" + output, status = capture("bin/rspec", env: { "ENFORCED_LOCALES" => "en,ca,es", "SKIP_NORMALIZATION" => "true" }) + + unless status.success? + run("git restore .") + puts output + exit_with_errors("Tests execution failed. Fix the errors and run again.") + end + end + + # Generates the changelog taking into account the last time the version changed + # + # @return [void] + def generate_changelog + sha_version = capture("git log -n 1 --pretty=format:%h -- .decidim-version")[0] + ChangeLogGenerator.new(token: @token, since_sha: sha_version).call + temporary_changelog = File.read("./temporary_changelog.md") + legacy_changelog = File.read("./CHANGELOG.md") + version_changelog = "## [#{version_number}](https://github.com/decidim/decidim/tree/#{version_number})\n\n#{temporary_changelog}\n" + changelog = legacy_changelog.gsub("# Changelog\n\n", "# Changelog\n\n#{version_changelog}") + File.write("./CHANGELOG.md", changelog) + end + + # Creates the pull request for bumping the version + # + # @return [void] + def create_pull_request + base_branch = release_branch + head_branch = "chore/prepare/#{version_number}" + + params = { + title: "Bump to v#{version_number} version", + body: "#### :tophat: What? Why? + +This PR prepares version of the #{release_branch} branch, so we can publish the release once this is approved and merged. + +#### Testing + +All the tests should pass, except for some generators tests, that will fail because the gems and NPM packages have not +been actually published yet (as in sent to rubygems/npm). +You will see errors such as `No matching version found for @decidim/browserslist-config@~0.xx.y` in the CI logs. + +:hearts: Thank you! + ", + labels: ["type: internal"], + head: head_branch, + base: base_branch + } + Decidim::MaintainersToolbox::GithubManager::Poster.new(token: @token, params: params).call + end + + # Changes the decidim version in the file + # + # @return [void] + def bump_decidim_version(new_version_number) + File.write(DECIDIM_VERSION_FILE, new_version_number) + end + end + end +end + diff --git a/spec/lib/decidim/maintainers_toolbox/backporter_spec.rb b/spec/lib/decidim/maintainers_toolbox/backporter_spec.rb index f747640..de4ea7d 100644 --- a/spec/lib/decidim/maintainers_toolbox/backporter_spec.rb +++ b/spec/lib/decidim/maintainers_toolbox/backporter_spec.rb @@ -2,7 +2,7 @@ require "decidim/maintainers_toolbox/backporter" -describe Decidim::MaintainersToolbox::Backporter do +RSpec.describe Decidim::MaintainersToolbox::Backporter do subject { described_class.new(token: token, pull_request_id: pull_request_id, version_number: version_number, exit_with_unstaged_changes: exit_with_unstaged_changes) } let(:token) { "1234" } diff --git a/spec/lib/decidim/maintainers_toolbox/backports_reporter/cli_report_spec.rb b/spec/lib/decidim/maintainers_toolbox/backports_reporter/cli_report_spec.rb index 2b737f0..d55d4d9 100644 --- a/spec/lib/decidim/maintainers_toolbox/backports_reporter/cli_report_spec.rb +++ b/spec/lib/decidim/maintainers_toolbox/backports_reporter/cli_report_spec.rb @@ -2,7 +2,7 @@ require "decidim/maintainers_toolbox/backports_reporter/cli_report" -describe Decidim::MaintainersToolbox::BackportsReporter::CLIReport do +RSpec.describe Decidim::MaintainersToolbox::BackportsReporter::CLIReport do subject { described_class.new(report: report, last_version_number: last_version_number).call } let(:report) do diff --git a/spec/lib/decidim/maintainers_toolbox/backports_reporter/csv_report_spec.rb b/spec/lib/decidim/maintainers_toolbox/backports_reporter/csv_report_spec.rb index 8e139e8..4b581ae 100644 --- a/spec/lib/decidim/maintainers_toolbox/backports_reporter/csv_report_spec.rb +++ b/spec/lib/decidim/maintainers_toolbox/backports_reporter/csv_report_spec.rb @@ -2,7 +2,7 @@ require "decidim/maintainers_toolbox/backports_reporter/csv_report" -describe Decidim::MaintainersToolbox::BackportsReporter::CSVReport do +RSpec.describe Decidim::MaintainersToolbox::BackportsReporter::CSVReport do subject { described_class.new(report: report, last_version_number: last_version_number).call } let(:report) do diff --git a/spec/lib/decidim/maintainers_toolbox/git_backport_manager_spec.rb b/spec/lib/decidim/maintainers_toolbox/git_backport_manager_spec.rb index b4c17e3..9094459 100644 --- a/spec/lib/decidim/maintainers_toolbox/git_backport_manager_spec.rb +++ b/spec/lib/decidim/maintainers_toolbox/git_backport_manager_spec.rb @@ -4,7 +4,7 @@ require "decidim/maintainers_toolbox/git_backport_manager" -describe Decidim::MaintainersToolbox::GitBackportManager do +RSpec.describe Decidim::MaintainersToolbox::GitBackportManager do subject { described_class.new(pull_request_id: pull_request_id, release_branch: release_branch, backport_branch: backport_branch, working_dir: tmp_repository_dir) } let(:release_branch) { "release/0.99-stable" } diff --git a/spec/lib/decidim/maintainers_toolbox/github_manager/poster_spec.rb b/spec/lib/decidim/maintainers_toolbox/github_manager/poster_spec.rb index 7ccddfb..2bee948 100644 --- a/spec/lib/decidim/maintainers_toolbox/github_manager/poster_spec.rb +++ b/spec/lib/decidim/maintainers_toolbox/github_manager/poster_spec.rb @@ -3,7 +3,7 @@ require "decidim/maintainers_toolbox/github_manager/poster" require "webmock/rspec" -describe Decidim::MaintainersToolbox::GithubManager::Poster do +RSpec.describe Decidim::MaintainersToolbox::GithubManager::Poster do let(:poster) { described_class.new(token: "abc", params: { title: "Hello world", body: "This is a test" }) } before do diff --git a/spec/lib/decidim/maintainers_toolbox/github_manager/querier/by_issue_id_spec.rb b/spec/lib/decidim/maintainers_toolbox/github_manager/querier/by_issue_id_spec.rb index cc39dfd..f191dea 100644 --- a/spec/lib/decidim/maintainers_toolbox/github_manager/querier/by_issue_id_spec.rb +++ b/spec/lib/decidim/maintainers_toolbox/github_manager/querier/by_issue_id_spec.rb @@ -3,7 +3,7 @@ require "decidim/maintainers_toolbox/github_manager/querier" require "webmock/rspec" -describe Decidim::MaintainersToolbox::GithubManager::Querier::ByIssueId do +RSpec.describe Decidim::MaintainersToolbox::GithubManager::Querier::ByIssueId do let(:querier) { described_class.new(token: "abc", issue_id: 12_345) } before do diff --git a/spec/lib/decidim/maintainers_toolbox/github_manager/querier/by_label_spec.rb b/spec/lib/decidim/maintainers_toolbox/github_manager/querier/by_label_spec.rb index 333cdc6..1fa779a 100644 --- a/spec/lib/decidim/maintainers_toolbox/github_manager/querier/by_label_spec.rb +++ b/spec/lib/decidim/maintainers_toolbox/github_manager/querier/by_label_spec.rb @@ -4,7 +4,7 @@ require "webmock/rspec" require "active_support/testing/time_helpers" -describe Decidim::MaintainersToolbox::GithubManager::Querier::ByLabel do +RSpec.describe Decidim::MaintainersToolbox::GithubManager::Querier::ByLabel do include ActiveSupport::Testing::TimeHelpers let(:querier) { described_class.new(token: "abc", days_to_check_from: days_to_check_from) } diff --git a/spec/lib/decidim/maintainers_toolbox/github_manager/querier/by_query_spec.rb b/spec/lib/decidim/maintainers_toolbox/github_manager/querier/by_query_spec.rb index 54c6da7..d858b6f 100644 --- a/spec/lib/decidim/maintainers_toolbox/github_manager/querier/by_query_spec.rb +++ b/spec/lib/decidim/maintainers_toolbox/github_manager/querier/by_query_spec.rb @@ -3,7 +3,7 @@ require "decidim/maintainers_toolbox/github_manager/querier" require "webmock/rspec" -describe Decidim::MaintainersToolbox::GithubManager::Querier::ByQuery do +RSpec.describe Decidim::MaintainersToolbox::GithubManager::Querier::ByQuery do let(:querier) { described_class.new(token: @token, query: { title: "Fix whatever" }) } let(:title) { "Fix whatever" } diff --git a/spec/lib/decidim/maintainers_toolbox/github_manager/querier/related_issues_spec.rb b/spec/lib/decidim/maintainers_toolbox/github_manager/querier/related_issues_spec.rb index d5e94ba..81de9cd 100644 --- a/spec/lib/decidim/maintainers_toolbox/github_manager/querier/related_issues_spec.rb +++ b/spec/lib/decidim/maintainers_toolbox/github_manager/querier/related_issues_spec.rb @@ -3,7 +3,7 @@ require "decidim/maintainers_toolbox/github_manager/querier" require "webmock/rspec" -describe Decidim::MaintainersToolbox::GithubManager::Querier::RelatedIssues do +RSpec.describe Decidim::MaintainersToolbox::GithubManager::Querier::RelatedIssues do let(:querier) { described_class.new(token: "abc", issue_id: 12_345) } let(:response) do [ diff --git a/spec/lib/decidim/maintainers_toolbox/release_candidate_version_spec.rb b/spec/lib/decidim/maintainers_toolbox/release_candidate_version_spec.rb new file mode 100644 index 0000000..ce1aadd --- /dev/null +++ b/spec/lib/decidim/maintainers_toolbox/release_candidate_version_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require "decidim/maintainers_toolbox/release_candidate_version" +require_relative "../../../shared/releaser_repository_shared_context" + +RSpec.describe Decidim::MaintainersToolbox::ReleaseCandidateVersion do + subject { described_class.new(token: token, working_dir: tmp_repository_dir) } + + let(:token) { "1234" } + let(:tmp_repository_dir) { "/tmp/decidim-release-candidate-version-test-#{rand(1_000)}" } + + include_context "releaser repository" + + describe "#release_branch" do + it "returns the correct branch" do + expect(subject.send(:release_branch)).to eq release_branch + end + end + + describe "#next_version_number_for_release_candidate" do + context "when it is a dev version" do + let(:version_number) { "0.1.0.dev" } + + it "returns the first release candidate" do + expect(subject.send(:next_version_number_for_release_candidate, version_number)).to eq "0.1.0.rc1" + end + end + + context "when it is a release candidate version" do + let(:version_number) { "0.1.0.rc1" } + + it "returns the correct next version number" do + expect(subject.send(:next_version_number_for_release_candidate, version_number)).to eq "0.1.0.rc2" + end + end + + context "when it is a patch version" do + let(:version_number) { "0.1.0" } + + it "raises an error" do + expect { subject.send(:next_version_number_for_release_candidate, version_number) }.to raise_error(Decidim::MaintainersToolbox::ReleaserUtils::InvalidVersionTypeError) + end + end + end + + context "if this is release branch with a patch version" do + let(:decidim_version) { "0.99.0" } + let(:release_branch) { "release/0.99-stable" } + + it "exits" do + expect { subject.call }.to raise_error SystemExit + end + end + + context "if this is feature branch" do + let(:decidim_version) { "0.99.0.rc1" } + let(:release_branch) { "feature/fix-my-street" } + + it "exits" do + expect { subject.call }.to raise_error SystemExit + end + end +end diff --git a/spec/lib/decidim/maintainers_toolbox/release_patch_version_spec.rb b/spec/lib/decidim/maintainers_toolbox/release_patch_version_spec.rb new file mode 100644 index 0000000..ff37929 --- /dev/null +++ b/spec/lib/decidim/maintainers_toolbox/release_patch_version_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require "decidim/maintainers_toolbox/release_patch_version" +require_relative "../../../shared/releaser_repository_shared_context" + +RSpec.describe Decidim::MaintainersToolbox::ReleasePatchVersion do + subject { described_class.new(token: token, working_dir: tmp_repository_dir) } + + let(:token) { "1234" } + let(:tmp_repository_dir) { "/tmp/decidim-release-patch-version-test-#{rand(1_000)}" } + + include_context "releaser repository" + + describe "#release_branch" do + it "returns the correct branch" do + expect(subject.send(:release_branch)).to eq release_branch + end + end + + describe "#next_version_number_for_patch_release" do + context "when it is a dev version" do + let(:version_number) { "0.1.0.dev" } + + it "returns the correct next version number" do + expect { subject.send(:next_version_number_for_patch_release, version_number) }.to raise_error(Decidim::MaintainersToolbox::ReleaserUtils::InvalidVersionTypeError) + end + end + + context "when it is a release candidate version" do + let(:version_number) { "0.1.0.rc1" } + + it "returns first patch number" do + expect(subject.send(:next_version_number_for_patch_release, version_number)).to eq "0.1.0" + end + end + + context "when it is a patch version" do + let(:version_number) { "0.1.0" } + + it "raises an error" do + expect(subject.send(:next_version_number_for_patch_release, version_number)).to eq "0.1.1" + end + end + end + + context "if this is not a release branch" do + it "exits" do + ` + git switch --quiet develop + ` + + expect { subject.call }.to raise_error SystemExit + end + end +end diff --git a/spec/lib/decidim/maintainers_toolbox/releaser_spec.rb b/spec/lib/decidim/maintainers_toolbox/releaser_spec.rb index 7420f0c..bdb1d3f 100644 --- a/spec/lib/decidim/maintainers_toolbox/releaser_spec.rb +++ b/spec/lib/decidim/maintainers_toolbox/releaser_spec.rb @@ -1,171 +1,24 @@ # frozen_string_literal: true require "decidim/maintainers_toolbox/releaser" +require_relative "../../../shared/releaser_repository_shared_context" -describe Decidim::MaintainersToolbox::Releaser do +RSpec.describe Decidim::MaintainersToolbox::Releaser do subject { described_class.new(token: token, version_type: version_type, exit_with_unstaged_changes: exit_with_unstaged_changes, working_dir: tmp_repository_dir) } let(:token) { "1234" } let(:version_type) { "patch" } - let(:release_branch) { "release/0.99-stable" } - let(:tmp_repository_dir) { "/tmp/decidim-releaser-test-#{rand(1_000)}" } - let(:working_dir) { File.expand_path("../../..", __dir__) } let(:exit_with_unstaged_changes) { true } - let(:decidim_version) { "0.99.0.rc1" } - - before do - FileUtils.mkdir_p("#{tmp_repository_dir}/code") - Dir.chdir("#{tmp_repository_dir}/code") - ` - git init --initial-branch=develop . - git config user.email "decidim_releaser@example.com" - git config user.name "Decidim::Releaser test" - - touch a_file.txt && git add a_file.txt - echo #{decidim_version} > .decidim-version && git add .decidim-version - git commit -m "Initial commit (#1234)" - - git branch #{release_branch} - git switch --quiet #{release_branch} - ` - end - - after do - Dir.chdir(working_dir) - FileUtils.rm_r(Dir.glob(tmp_repository_dir)) - end - - describe "#branch" do - it "returns the correct branch" do - expect(subject.send(:branch)).to eq release_branch - end - end - - describe "#release_branch" do - it "returns the correct branch" do - expect(subject.send(:release_branch)).to eq release_branch - end - - context "when we are not in a release branch" do - it "raises an error" do - `git switch --quiet develop` - expect { subject.send(:release_branch) }.to raise_error(Decidim::MaintainersToolbox::Releaser::InvalidBranchError) - end - end - end - - describe "#bump_decidim_version" do - context "when it is a release candidate" do - let(:decidim_version) { "0.99.0.rc1" } - let(:version_type) { "rc" } - - it "changes the version number in the decidim version file" do - version_number = File.read(".decidim-version").strip - expect(version_number).to eq("0.99.0.rc1") - - subject.send(:bump_decidim_version) - new_version_number = File.read(".decidim-version").strip - - expect(new_version_number).to eq("0.99.0.rc2") - end - end - - context "when it is a patch release" do - let(:decidim_version) { "0.99.0" } - let(:version_type) { "patch" } - - it "changes the version number in the decidim version file" do - subject.send(:bump_decidim_version) - new_version_number = File.read(".decidim-version").strip - - expect(new_version_number).to eq("0.99.1") - end - end - end - - describe "#parsed_version_number" do - context "when it is a dev version" do - let(:version_number) { "1.2.3.dev" } - - it "parses the version number" do - expect(subject.send(:parsed_version_number, version_number)).to eq([1, 2, 3]) - end - end - - context "when it is a release candidate version" do - let(:version_number) { "1.2.3.rc1" } - - it "parses the version number" do - expect(subject.send(:parsed_version_number, version_number)).to eq([1, 2, 3]) - end - end - - context "when it is a patch version" do - let(:version_number) { "1.2.3" } - - it "parses the version number" do - expect(subject.send(:parsed_version_number, version_number)).to eq([1, 2, 3]) - end - end - end - - describe "#next_version_number_for_release_candidate" do - context "when it is a dev version" do - let(:version_number) { "0.1.0.dev" } - - it "returns the first release candidate" do - expect(subject.send(:next_version_number_for_release_candidate, version_number)).to eq "0.1.0.rc1" - end - end - - context "when it is a release candidate version" do - let(:version_number) { "0.1.0.rc1" } - - it "returns the correct next version number" do - expect(subject.send(:next_version_number_for_release_candidate, version_number)).to eq "0.1.0.rc2" - end - end - context "when it is a patch version" do - let(:version_number) { "0.1.0" } - - it "raises an error" do - expect { subject.send(:next_version_number_for_release_candidate, version_number) }.to raise_error(Decidim::MaintainersToolbox::Releaser::InvalidVersionTypeError) - end - end - end - - describe "#next_version_number_for_patch_release" do - context "when it is a dev version" do - let(:version_number) { "0.1.0.dev" } - - it "returns the correct next version number" do - expect { subject.send(:next_version_number_for_patch_release, version_number) }.to raise_error(Decidim::MaintainersToolbox::Releaser::InvalidVersionTypeError) - end - end - - context "when it is a release candidate version" do - let(:version_number) { "0.1.0.rc1" } - - it "returns first patch number" do - expect(subject.send(:next_version_number_for_patch_release, version_number)).to eq "0.1.0" - end - end - - context "when it is a patch version" do - let(:version_number) { "0.1.0" } + let(:tmp_repository_dir) { "/tmp/decidim-releaser-test-#{rand(1_000)}" } - it "raises an error" do - expect(subject.send(:next_version_number_for_patch_release, version_number)).to eq "0.1.1" - end - end - end + include_context "releaser repository" - describe "#old_version_number" do - let(:decidim_version) { "0.1.0" } + context "if there are unstaged changes" do + it "exits" do + `echo hello_world.txt > a_file.txt` - it "returns the correct version number" do - expect(subject.send(:old_version_number)).to eq "0.1.0" + expect { subject.call }.to raise_error SystemExit end end end diff --git a/spec/lib/decidim/maintainers_toolbox/releaser_utils_spec.rb b/spec/lib/decidim/maintainers_toolbox/releaser_utils_spec.rb new file mode 100644 index 0000000..87517c7 --- /dev/null +++ b/spec/lib/decidim/maintainers_toolbox/releaser_utils_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require "decidim/maintainers_toolbox/releaser" +require_relative "../../../shared/releaser_repository_shared_context" + +RSpec.describe Decidim::MaintainersToolbox::ReleaserUtils do + + subject do + class DummyClass + include Decidim::MaintainersToolbox::ReleaserUtils + end + + klass = DummyClass.new + klass + end + + let(:tmp_repository_dir) { "/tmp/decidim-releaser-utils-test-#{rand(1_000)}" } + + include_context "releaser repository" + + describe "#bump_decidim_version" do + it "writes the file" do + version_number = File.read(".decidim-version").strip + expect(version_number).to eq("0.99.0.rc1") + + subject.bump_decidim_version("0.99.0.rc2") + + new_version_number = File.read(".decidim-version").strip + expect(new_version_number).to eq("0.99.0.rc2") + end + end + + describe "#branch" do + it "returns the correct branch" do + expect(subject.send(:branch)).to eq release_branch + end + end + + describe "#parsed_version_number" do + context "when it is a dev version" do + let(:version_number) { "1.2.3.dev" } + + it "parses the version number" do + expect(subject.send(:parsed_version_number, version_number)).to eq([1, 2, 3]) + end + end + + context "when it is a release candidate version" do + let(:version_number) { "1.2.3.rc1" } + + it "parses the version number" do + expect(subject.send(:parsed_version_number, version_number)).to eq([1, 2, 3]) + end + end + + context "when it is a patch version" do + let(:version_number) { "1.2.3" } + + it "parses the version number" do + expect(subject.send(:parsed_version_number, version_number)).to eq([1, 2, 3]) + end + end + end + + describe "#old_version_number" do + let(:decidim_version) { "0.1.0" } + + it "returns the correct version number" do + expect(subject.send(:old_version_number)).to eq "0.1.0" + end + end +end diff --git a/spec/shared/releaser_repository_shared_context.rb b/spec/shared/releaser_repository_shared_context.rb new file mode 100644 index 0000000..bf8546c --- /dev/null +++ b/spec/shared/releaser_repository_shared_context.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +RSpec.shared_context "releaser repository" do + let(:working_dir) { File.expand_path("../../..", __dir__) } + let(:decidim_version) { "0.99.0.rc1" } + let(:release_branch) { "release/0.99-stable" } + + before do + FileUtils.mkdir_p("#{tmp_repository_dir}") + Dir.chdir("#{tmp_repository_dir}") + ` + git init --initial-branch=develop . + git config user.email "decidim_releaser@example.com" + git config user.name "Decidim::ReleaserUtils test" + + touch a_file.txt && git add a_file.txt + echo #{decidim_version} > .decidim-version && git add .decidim-version + git commit -m "Initial commit (#1234)" + + git branch #{release_branch} + git switch --quiet #{release_branch} + ` + end + + after do + Dir.chdir(working_dir) + FileUtils.rm_r(Dir.glob(tmp_repository_dir)) + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..012a059 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,96 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + + # The settings below are suggested to provide a good initial experience + # with RSpec, but feel free to customize to your heart's content. + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = ".rspec-failures" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # https://rspec.info/features/3-12/rspec-core/configuration/zero-monkey-patching-mode/ + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +end