Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ Gemfile.lock
.vagrant/
.yardoc/
/tmp/
/vendor/
1 change: 1 addition & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ AllCops:
Metrics/BlockLength:
Exclude:
- "owlbot-postprocessor/test/test_*.rb"
- "test/**/*"
Metrics/ClassLength:
Max: 300
Metrics/MethodLength:
Expand Down
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
source "https://rubygems.org"

gem "gems", "~> 1.3"
gem "google-style", "~> 1.32.0"
gem "jwt", "~> 2.10"
gem "minitest-mock", "~> 5.27"
gem "minitest-reporters", "~> 1.8.0"
gem "toys", "~> 0.22"
190 changes: 190 additions & 0 deletions test/release_perform_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
# frozen_string_literal: true

# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

require "minitest/autorun"
require "toys"
require "fileutils"
require "tmpdir"
require "gems"
require "json"

module Gems
class Client
def info _gem_name = nil
{ "version" => "0.1.0" }
end

def push _file = nil
raise "Gems::Client#push should NOT be called"
end
end
end

class ReleasePerformTest < Minitest::Test
def setup
@orig_cwd = Dir.pwd
@tmpdir = Dir.mktmpdir
@dummy_gem_dir = File.join @tmpdir, "dummy_gem"
FileUtils.mkdir_p @dummy_gem_dir
setup_dummy_gem_files
end

def teardown
Dir.chdir @orig_cwd if @orig_cwd
FileUtils.rm_rf @tmpdir
end

def test_docs_only_behavior
cli = Toys::StandardCLI.new
cli.loader.lookup ["release", "perform"]
performer_class = find_performer_class cli

calls = {}
with_mocked_performer performer_class, calls do
args = ["dummy_gem", "--base-dir=#{@dummy_gem_dir}", "--docs-only", "--dry-run"]
exit_code = cli.run "release", "perform", *args

assert_equal 0, exit_code
refute calls[:publish_gem], "publish_gem should NOT be called"
assert calls[:publish_docs], "publish_docs SHOULD be called"
assert calls[:publish_rad], "publish_rad SHOULD be called"
end
end

def test_normal_release_publishes_gem_when_outdated
cli = Toys::StandardCLI.new
cli.loader.lookup ["release", "perform"]
performer_class = find_performer_class cli

calls = {}
with_mocked_performer performer_class, calls, current_version: "0.0.0" do
args = [
"dummy_gem",
"--base-dir=#{@dummy_gem_dir}",
"--enable-docs",
"--enable-rad",
"--dry-run"
]
exit_code = cli.run "release", "perform", *args

assert_equal 0, exit_code
assert calls[:publish_gem], "publish_gem SHOULD be called"
assert calls[:publish_docs], "publish_docs SHOULD be called"
assert calls[:publish_rad], "publish_rad SHOULD be called"
end
end

def test_normal_release_skips_when_up_to_date
cli = Toys::StandardCLI.new
cli.loader.lookup ["release", "perform"]
performer_class = find_performer_class cli

calls = {}
with_mocked_performer performer_class, calls, current_version: "0.1.0" do
args = [
"dummy_gem",
"--base-dir=#{@dummy_gem_dir}",
"--enable-docs",
"--enable-rad",
"--dry-run"
]
exit_code = cli.run "release", "perform", *args

assert_equal 0, exit_code
refute calls[:publish_gem], "publish_gem should NOT be called"
refute calls[:publish_docs], "publish_docs should NOT be called (skipped early)"
refute calls[:publish_rad], "publish_rad should NOT be called (skipped early)"
end
end

private

def setup_dummy_gem_files
gemspec_content = <<~RUBY
Gem::Specification.new do |s|
s.name = "dummy_gem"
s.version = "0.1.0"
s.summary = "Dummy Gem for testing"
s.authors = ["Test"]
end
RUBY
metadata = { "name_pretty" => "Dummy Gem", "is_cloud" => true }
File.write File.join(@dummy_gem_dir, "dummy_gem.gemspec"), gemspec_content
File.write File.join(@dummy_gem_dir, ".repo-metadata.json"), JSON.dump(metadata)
File.write File.join(@dummy_gem_dir, ".yardopts"), "# Empty yardopts\n"
FileUtils.mkdir_p File.join(@dummy_gem_dir, "doc")
end

def find_performer_class cli
tool, = cli.loader.lookup ["release", "perform"]
tool_class = tool.tool_class
Toys::InputFile.constants.each do |const_name|
mod = Toys::InputFile.const_get const_name
next unless mod.is_a?(Module) && mod.instance_variable_get(:@__tool_class) == tool_class

return mod.const_get :Performer if mod.const_defined? :Performer, false
end
nil
end

def with_mocked_performer performer_class, calls, current_version: nil
orig_methods = save_original_methods performer_class
apply_mock_methods performer_class, calls, current_version
yield
ensure
restore_original_methods performer_class, orig_methods if performer_class && orig_methods
end

def save_original_methods performer_class
{
run_aux_task: performer_class.instance_method(:run_aux_task),
publish_gem: performer_class.instance_method(:publish_gem),
publish_docs: performer_class.instance_method(:publish_docs),
publish_rad: performer_class.instance_method(:publish_rad),
current_rubygems_version: performer_class.instance_method(:current_rubygems_version)
}
end

def apply_mock_methods performer_class, calls, current_version
performer_class.class_eval do
define_method :run_aux_task do |*_args|
nil
end
define_method :publish_gem do |*_args|
calls[:publish_gem] = true
end
define_method :publish_docs do |*_args|
calls[:publish_docs] = true
end
define_method :publish_rad do |*_args|
calls[:publish_rad] = true
end
if current_version
define_method :current_rubygems_version do
Gem::Version.new current_version
end
end
end
end

def restore_original_methods performer_class, orig_methods
performer_class.class_eval do
orig_methods.each do |name, method|
define_method name, method
end
end
end
end
32 changes: 17 additions & 15 deletions toys/release/perform.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@
flag :docs_staging_bucket, "--docs-staging-bucket=VALUE", default: ENV["STAGING_BUCKET"]
flag :rad_staging_bucket, "--rad-staging-bucket=VALUE", default: ENV["V2_STAGING_BUCKET"]
flag :docuploader_credentials, "--docuploader-credentials=VALUE", default: ENV["DOCUPLOADER_CREDENTIALS"]
flag :docs_only

include :exec, e: true
include :gems

def run
Dir.chdir context_directory
Expand All @@ -55,8 +55,8 @@ def run
end

def load_deps
gem "gems", "~> 1.3"
gem "jwt", "~> 2.10"
gem "gems", "~> 1.3" unless defined? Gems
gem "jwt", "~> 2.10" unless defined? JWT
require "fileutils"
require "gems"
require "json"
Expand Down Expand Up @@ -236,8 +236,10 @@ def perform_release_gem name:, last_version:
docuploader_tries: 2

releaser.run force_republish: force_republish,
enable_docs: enable_docs,
enable_rad: enable_rad,
dry_run: dry_run
dry_run: dry_run,
docs_only: docs_only
end

def determine_packages
Expand Down Expand Up @@ -354,39 +356,39 @@ def needs_gem_publish?
def run force_republish: false,
enable_docs: false,
enable_rad: false,
dry_run: false
if !force_republish && !needs_gem_publish?
dry_run: false,
docs_only: false
unless docs_only || force_republish || needs_gem_publish?
logger.warn "**** Gem #{gem_name} is already up to date at version #{gem_version}. Skipping."
return
end
transformation_info = transform_links
transformation_info = {}
begin
publish_gem dry_run: dry_run unless @gem_name == "help"
publish_docs dry_run: dry_run if enable_docs
publish_rad dry_run: dry_run if enable_rad
transform_links transformation_info
publish_gem dry_run: dry_run unless docs_only || @gem_name == "help"
publish_docs dry_run: dry_run if docs_only || enable_docs
publish_rad dry_run: dry_run if docs_only || enable_rad
ensure
detransform_links transformation_info
end
end

def transform_links
def transform_links transformation_info
logger.info "**** Transforming links for #{gem_name}"
transformation_info = {}
Dir.chdir gem_dir do
Dir.glob "*.md" do |filename|
content = File.read filename
transformation_info[filename] = content
transformed_content = content.gsub(/\[([^\]]*)\]\(([^):]*\.md)\)/, "{file:\\2 \\1}")
File.open(filename, "w") { |file| file << transformed_content }
File.write filename, transformed_content
end
end
transformation_info
end

def detransform_links transformation_info
Dir.chdir gem_dir do
transformation_info.each do |filename, content|
File.open(filename, "w") { |file| file << content }
File.write filename, content
end
end
end
Expand Down
Loading