diff --git a/toys/gapic/.toys.rb b/toys/gapic/.toys.rb index b53209e0..77640678 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 @@ -28,6 +29,29 @@ tool "yard", delegate_to: "yardoc" +tool "doctest" do + desc "Run yard-doctest example tests." + + 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 + 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"] + 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 + + expand :gem_build expand :gem_build, name: "install", install_gem: true @@ -45,7 +69,8 @@ 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 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], diff --git a/toys/gapic/doctest_mock_patch.rb b/toys/gapic/doctest_mock_patch.rb new file mode 100644 index 00000000..9f816d1f --- /dev/null +++ b/toys/gapic/doctest_mock_patch.rb @@ -0,0 +1,59 @@ +# 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. + +module Kernel + alias orig_require_for_doctest require + def require path + res = orig_require_for_doctest path + Kernel.patch_minitest_mock_if_needed! + res + 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 + end + end + end + end +end + +Kernel.patch_minitest_mock_if_needed!