From 8bf5587faf12838a44f58954a7c5e5d708644de7 Mon Sep 17 00:00:00 2001 From: Torrey Payne Date: Fri, 26 Jun 2026 21:21:08 +0000 Subject: [PATCH 01/10] feat(ci): add doctest task with file detection --- toys/gapic/.toys.rb | 19 +++++++++++++++++++ toys/gapic/ci.rb | 1 + 2 files changed, 20 insertions(+) diff --git a/toys/gapic/.toys.rb b/toys/gapic/.toys.rb index b53209e0..545e0604 100644 --- a/toys/gapic/.toys.rb +++ b/toys/gapic/.toys.rb @@ -28,6 +28,25 @@ tool "yard", delegate_to: "yardoc" +tool "doctest" do + desc "Run yard-doctest example tests." + + include :exec, e: true + + def run + unless File.exist? "support/doctest_helper.rb" + puts "No doctest helper present, skipping doctests." + exit 0 + end + Dir.chdir context_directory + Bundler.with_clean_env do + exec ["bundle", "exec", "yard", "config", "load_plugins", "true"] + exec ["bundle", "exec", "yard", "doctest"] + end + end +end + + expand :gem_build expand :gem_build, name: "install", install_gem: true diff --git a/toys/gapic/ci.rb b/toys/gapic/ci.rb index c4504896..2e1de0fd 100644 --- a/toys/gapic/ci.rb +++ b/toys/gapic/ci.rb @@ -19,6 +19,7 @@ "rubocop" => [], "build" => [], "yard" => [], + "doctest" => [], "linkinator" => [], "acceptance" => [:project, :keyfile], "samples-main" => [:project, :keyfile, :samples_bundle_update], From b9b549d016d53f57550ae436e0c8e2fa9b956cc9 Mon Sep 17 00:00:00 2001 From: Torrey Payne Date: Fri, 26 Jun 2026 23:09:36 +0000 Subject: [PATCH 02/10] fix(toys): support unbundled_env for bundler 4 compatibility --- toys/gapic/.toys.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/toys/gapic/.toys.rb b/toys/gapic/.toys.rb index 545e0604..f1324fe0 100644 --- a/toys/gapic/.toys.rb +++ b/toys/gapic/.toys.rb @@ -15,6 +15,7 @@ # limitations under the License. toys_version! ">= 0.15.3" +require "bundler" expand :clean, paths: :gitignore @@ -34,12 +35,14 @@ include :exec, e: true def run + require "bundler" unless File.exist? "support/doctest_helper.rb" puts "No doctest helper present, skipping doctests." exit 0 end Dir.chdir context_directory - Bundler.with_clean_env do + env_method = Bundler.respond_to?(:with_unbundled_env) ? :with_unbundled_env : :with_clean_env + Bundler.send(env_method) do exec ["bundle", "exec", "yard", "config", "load_plugins", "true"] exec ["bundle", "exec", "yard", "doctest"] end @@ -64,7 +67,8 @@ def run def run Dir.chdir context_directory - Bundler.with_clean_env do + env_method = Bundler.respond_to?(:with_unbundled_env) ? :with_unbundled_env : :with_clean_env + Bundler.send(env_method) do exec ["bundle", update ? "update" : "install"] end end From 6880e2f9584c554750fb84d976d1492cfd58d9ae Mon Sep 17 00:00:00 2001 From: Torrey Payne <11740989+torreypayne@users.noreply.github.com> Date: Tue, 4 Aug 2026 17:02:37 +0000 Subject: [PATCH 03/10] style(toys): omit parentheses in Bundler.send call for rubocop compliance --- toys/gapic/.toys.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/toys/gapic/.toys.rb b/toys/gapic/.toys.rb index f1324fe0..d2714f51 100644 --- a/toys/gapic/.toys.rb +++ b/toys/gapic/.toys.rb @@ -42,7 +42,7 @@ def run end Dir.chdir context_directory env_method = Bundler.respond_to?(:with_unbundled_env) ? :with_unbundled_env : :with_clean_env - Bundler.send(env_method) do + Bundler.send env_method do exec ["bundle", "exec", "yard", "config", "load_plugins", "true"] exec ["bundle", "exec", "yard", "doctest"] end @@ -68,7 +68,7 @@ def run def run Dir.chdir context_directory env_method = Bundler.respond_to?(:with_unbundled_env) ? :with_unbundled_env : :with_clean_env - Bundler.send(env_method) do + Bundler.send env_method do exec ["bundle", update ? "update" : "install"] end end From 7840019af33df46423f6f87af8684304cf3b7417 Mon Sep 17 00:00:00 2001 From: Torrey Payne <11740989+torreypayne@users.noreply.github.com> Date: Tue, 4 Aug 2026 17:15:30 +0000 Subject: [PATCH 04/10] feat(toys): set MT_KWARGS_HACK=1 environment variable for yard doctest execution --- toys/gapic/.toys.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toys/gapic/.toys.rb b/toys/gapic/.toys.rb index d2714f51..2f3fd2c4 100644 --- a/toys/gapic/.toys.rb +++ b/toys/gapic/.toys.rb @@ -44,7 +44,7 @@ def run env_method = Bundler.respond_to?(:with_unbundled_env) ? :with_unbundled_env : :with_clean_env Bundler.send env_method do exec ["bundle", "exec", "yard", "config", "load_plugins", "true"] - exec ["bundle", "exec", "yard", "doctest"] + exec ["bundle", "exec", "yard", "doctest"], env: { "MT_KWARGS_HACK" => "1" } end end end From b2108e5bb4ec66cd7bf975cfe505dab4a3279fdc Mon Sep 17 00:00:00 2001 From: Torrey Payne <11740989+torreypayne@users.noreply.github.com> Date: Tue, 4 Aug 2026 21:57:12 +0000 Subject: [PATCH 05/10] feat(toys): add Minitest::Mock patch for yard doctest keyword argument compatibility --- toys/gapic/.toys.rb | 4 +++- toys/gapic/doctest_mock_patch.rb | 30 ++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 toys/gapic/doctest_mock_patch.rb diff --git a/toys/gapic/.toys.rb b/toys/gapic/.toys.rb index 2f3fd2c4..77640678 100644 --- a/toys/gapic/.toys.rb +++ b/toys/gapic/.toys.rb @@ -44,7 +44,9 @@ def run env_method = Bundler.respond_to?(:with_unbundled_env) ? :with_unbundled_env : :with_clean_env Bundler.send env_method do exec ["bundle", "exec", "yard", "config", "load_plugins", "true"] - exec ["bundle", "exec", "yard", "doctest"], env: { "MT_KWARGS_HACK" => "1" } + patch_path = File.expand_path "doctest_mock_patch.rb", __dir__ + rubyopt = [ENV["RUBYOPT"], "-r#{patch_path}"].compact.join " " + exec ["bundle", "exec", "yard", "doctest"], env: { "MT_KWARGS_HACK" => "1", "RUBYOPT" => rubyopt } end end end diff --git a/toys/gapic/doctest_mock_patch.rb b/toys/gapic/doctest_mock_patch.rb new file mode 100644 index 00000000..6e3e4e86 --- /dev/null +++ b/toys/gapic/doctest_mock_patch.rb @@ -0,0 +1,30 @@ +# 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 +# +# https://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/mock" + +module Minitest + class Mock + def expect name, retval, args = [], **kwargs, &blk + if args.is_a?(Array) && args.last == Hash + args = args[0...-1] + end + kwargs = Hash if kwargs.empty? + @expected_calls[name] << { retval: retval, args: args, kwargs: kwargs, block: blk } + self + end + end +end From f9c8018a6b905cbdfcd08aafa2983284359939b6 Mon Sep 17 00:00:00 2001 From: Torrey Payne <11740989+torreypayne@users.noreply.github.com> Date: Tue, 4 Aug 2026 21:57:43 +0000 Subject: [PATCH 06/10] fix(toys): use lazy Kernel.require hook in doctest_mock_patch to avoid bundler version conflicts --- toys/gapic/doctest_mock_patch.rb | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/toys/gapic/doctest_mock_patch.rb b/toys/gapic/doctest_mock_patch.rb index 6e3e4e86..8a3b9499 100644 --- a/toys/gapic/doctest_mock_patch.rb +++ b/toys/gapic/doctest_mock_patch.rb @@ -14,17 +14,23 @@ # See the License for the specific language governing permissions and # limitations under the License. -require "minitest/mock" - -module Minitest - class Mock - def expect name, retval, args = [], **kwargs, &blk - if args.is_a?(Array) && args.last == Hash - args = args[0...-1] +module Kernel + alias orig_require_for_doctest require + def require path + res = orig_require_for_doctest path + if path == "minitest/mock" && defined?(Minitest::Mock) && !Minitest::Mock.instance_methods(false).include?(:orig_expect_for_doctest) + Minitest::Mock.class_eval do + alias orig_expect_for_doctest expect + def expect name, retval, args = [], **kwargs, &blk + if args.is_a?(Array) && args.last == Hash + args = args[0...-1] + end + kwargs = Hash if kwargs.empty? + @expected_calls[name] << { retval: retval, args: args, kwargs: kwargs, block: blk } + self + end end - kwargs = Hash if kwargs.empty? - @expected_calls[name] << { retval: retval, args: args, kwargs: kwargs, block: blk } - self end + res end end From 5c8fa4ea68c0d0e652a2473afdb8482197ff041d Mon Sep 17 00:00:00 2001 From: Torrey Payne <11740989+torreypayne@users.noreply.github.com> Date: Tue, 4 Aug 2026 21:59:43 +0000 Subject: [PATCH 07/10] fix(toys): add method_missing rescue in doctest_mock_patch for minitest 6 compatibility --- toys/gapic/doctest_mock_patch.rb | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/toys/gapic/doctest_mock_patch.rb b/toys/gapic/doctest_mock_patch.rb index 8a3b9499..71c88d70 100644 --- a/toys/gapic/doctest_mock_patch.rb +++ b/toys/gapic/doctest_mock_patch.rb @@ -18,16 +18,25 @@ module Kernel alias orig_require_for_doctest require def require path res = orig_require_for_doctest path - if path == "minitest/mock" && defined?(Minitest::Mock) && !Minitest::Mock.instance_methods(false).include?(:orig_expect_for_doctest) + if path == "minitest/mock" && defined?(Minitest::Mock) && + !Minitest::Mock.instance_methods(false).include?(:orig_expect_for_doctest) Minitest::Mock.class_eval do alias orig_expect_for_doctest expect def expect name, retval, args = [], **kwargs, &blk - if args.is_a?(Array) && args.last == Hash - args = args[0...-1] - end - kwargs = Hash if kwargs.empty? - @expected_calls[name] << { retval: retval, args: args, kwargs: kwargs, block: blk } - self + args = args[0...-1] if args.is_a?(Array) && args.last == Hash + orig_expect_for_doctest name, retval, args, **kwargs, &blk + end + + alias orig_method_missing_for_doctest method_missing + def method_missing sym, *args, **kwargs, &block + orig_method_missing_for_doctest sym, *args, **kwargs, &block + rescue ArgumentError => e + raise unless e.message.include? "keyword arguments" + orig_method_missing_for_doctest sym, *args, &block + end + + def respond_to_missing? sym, include_private = false + super end end end From a1f9d3b86c4c513c714efc15863d90704b37a17a Mon Sep 17 00:00:00 2001 From: Torrey Payne <11740989+torreypayne@users.noreply.github.com> Date: Tue, 4 Aug 2026 22:04:43 +0000 Subject: [PATCH 08/10] fix(toys): refine doctest_mock_patch loading hook for minitest 6 compatibility --- toys/gapic/doctest_mock_patch.rb | 43 ++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/toys/gapic/doctest_mock_patch.rb b/toys/gapic/doctest_mock_patch.rb index 71c88d70..2f2abfb6 100644 --- a/toys/gapic/doctest_mock_patch.rb +++ b/toys/gapic/doctest_mock_patch.rb @@ -18,28 +18,33 @@ module Kernel alias orig_require_for_doctest require def require path res = orig_require_for_doctest path - if path == "minitest/mock" && defined?(Minitest::Mock) && - !Minitest::Mock.instance_methods(false).include?(:orig_expect_for_doctest) - Minitest::Mock.class_eval do - alias orig_expect_for_doctest expect - def expect name, retval, args = [], **kwargs, &blk - args = args[0...-1] if args.is_a?(Array) && args.last == Hash - orig_expect_for_doctest name, retval, args, **kwargs, &blk - end + patch_minitest_mock_if_needed! + res + end - alias orig_method_missing_for_doctest method_missing - def method_missing sym, *args, **kwargs, &block - orig_method_missing_for_doctest sym, *args, **kwargs, &block - rescue ArgumentError => e - raise unless e.message.include? "keyword arguments" - orig_method_missing_for_doctest sym, *args, &block - end + def patch_minitest_mock_if_needed! + return unless defined?(Minitest::Mock) + return if Minitest::Mock.instance_methods(false).include? :orig_expect_for_doctest + Minitest::Mock.class_eval do + alias orig_expect_for_doctest expect + def expect name, retval, args = [], **kwargs, &blk + args = args[0...-1] if args.is_a?(Array) && args.last == Hash + orig_expect_for_doctest name, retval, args, **kwargs, &blk + end - def respond_to_missing? sym, include_private = false - super - end + alias orig_method_missing_for_doctest method_missing + def method_missing sym, *args, **kwargs, &block + orig_method_missing_for_doctest sym, *args, **kwargs, &block + rescue ArgumentError => e + raise unless e.message.include? "keyword arguments" + orig_method_missing_for_doctest sym, *args, &block + end + + def respond_to_missing? sym, include_private = false + super end end - res end end + +patch_minitest_mock_if_needed! From a68f3c99a13f2a463cebacd6d9e2da89e0dd0c67 Mon Sep 17 00:00:00 2001 From: Torrey Payne <11740989+torreypayne@users.noreply.github.com> Date: Tue, 4 Aug 2026 22:06:02 +0000 Subject: [PATCH 09/10] fix(toys): hook all Kernel require methods for minitest 6 compatibility in yard doctests --- toys/gapic/doctest_mock_patch.rb | 56 ++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/toys/gapic/doctest_mock_patch.rb b/toys/gapic/doctest_mock_patch.rb index 2f2abfb6..8371bc28 100644 --- a/toys/gapic/doctest_mock_patch.rb +++ b/toys/gapic/doctest_mock_patch.rb @@ -18,33 +18,49 @@ module Kernel alias orig_require_for_doctest require def require path res = orig_require_for_doctest path - patch_minitest_mock_if_needed! + Kernel.patch_minitest_mock_if_needed! res end - def patch_minitest_mock_if_needed! - return unless defined?(Minitest::Mock) - return if Minitest::Mock.instance_methods(false).include? :orig_expect_for_doctest - Minitest::Mock.class_eval do - alias orig_expect_for_doctest expect - def expect name, retval, args = [], **kwargs, &blk - args = args[0...-1] if args.is_a?(Array) && args.last == Hash - orig_expect_for_doctest name, retval, args, **kwargs, &blk - end + alias orig_require_relative_for_doctest require_relative + def require_relative path + res = orig_require_relative_for_doctest path + Kernel.patch_minitest_mock_if_needed! + res + end - alias orig_method_missing_for_doctest method_missing - def method_missing sym, *args, **kwargs, &block - orig_method_missing_for_doctest sym, *args, **kwargs, &block - rescue ArgumentError => e - raise unless e.message.include? "keyword arguments" - orig_method_missing_for_doctest sym, *args, &block - end + class << self + alias orig_singleton_require_for_doctest require + def require path + res = orig_singleton_require_for_doctest path + Kernel.patch_minitest_mock_if_needed! + res + end + + def patch_minitest_mock_if_needed! + return unless defined?(Minitest::Mock) + return if Minitest::Mock.instance_methods(false).include? :orig_expect_for_doctest + Minitest::Mock.class_eval do + alias orig_expect_for_doctest expect + def expect name, retval, args = [], **kwargs, &blk + args = args[0...-1] if args.is_a?(Array) && args.last == Hash + orig_expect_for_doctest name, retval, args, **kwargs, &blk + end + + alias orig_method_missing_for_doctest method_missing + def method_missing sym, *args, **kwargs, &block + orig_method_missing_for_doctest sym, *args, **kwargs, &block + rescue ArgumentError => e + raise unless e.message.include? "keyword arguments" + orig_method_missing_for_doctest sym, *args, &block + end - def respond_to_missing? sym, include_private = false - super + def respond_to_missing? sym, include_private = false + super + end end end end end -patch_minitest_mock_if_needed! +Kernel.patch_minitest_mock_if_needed! From 7a9a7f401000cbae97d301895a4c4e7b21abe731 Mon Sep 17 00:00:00 2001 From: Torrey Payne <11740989+torreypayne@users.noreply.github.com> Date: Tue, 4 Aug 2026 22:06:31 +0000 Subject: [PATCH 10/10] fix(toys): remove require_relative hook from doctest_mock_patch --- toys/gapic/doctest_mock_patch.rb | 7 ------- 1 file changed, 7 deletions(-) diff --git a/toys/gapic/doctest_mock_patch.rb b/toys/gapic/doctest_mock_patch.rb index 8371bc28..9f816d1f 100644 --- a/toys/gapic/doctest_mock_patch.rb +++ b/toys/gapic/doctest_mock_patch.rb @@ -22,13 +22,6 @@ def require path res end - alias orig_require_relative_for_doctest require_relative - def require_relative path - res = orig_require_relative_for_doctest path - Kernel.patch_minitest_mock_if_needed! - res - end - class << self alias orig_singleton_require_for_doctest require def require path