# frozen_string_literal: true
require "cartage"
##
# Provide helper methods for testing Cartage and plug-ins using Minitest.
module Cartage::Minitest
# :nocov:
##
# A helper to stub ENV lookups against an +env+ hash. If +options+ has a key
# :passthrough, will reach for the original ENV value.
#
# Tested thoroughly only with Minitest::Moar::Stubbing. This helper will
# probably be moved to a different gem in the future.
def stub_env(env, options = {}, *block_args, &block)
mock = lambda { |key|
env.fetch(key) { |k|
ENV.send(:'__minitest_stub__[]', k) if options[:passthrough]
}
}
if defined?(Minitest::Moar::Stubbing)
stub ENV, :[], mock, *block_args, &block
else
ENV.stub :[], mock, *block_args, &block
end
end
# A helper to stub capturing shell calls (`echo true` or %x(echo
# true)) to return +value+.
#
# This helper will probably be moved to a different gem in the future.
def stub_backticks(value)
Kernel.send(:alias_method, :__minitest_stub_backticks__, :`)
Kernel.send(:define_method, :`) { |*| value }
yield
ensure
Kernel.send(:remove_method, :`)
Kernel.send(:alias_method, :`, :__minitest_stub_backticks__)
Kernel.send(:remove_method, :__minitest_stub_backticks__)
end
# A helper to stub Cartage#repo_url to return +value+.
def stub_cartage_repo_url(value = nil, &block)
stub_instance_method Cartage, :repo_url, -> { value || "git://host/repo-url.git" },
&block
end
##
# An assertion that Pathname#write receives the +expected+ value.
def assert_pathname_write(expected)
string_io = StringIO.new
stub_instance_method Pathname, :write, ->(v) { string_io.write(v) } do
yield
assert_equal expected, String.new(string_io.string)
end
end
# A helper to stub Pathname#expand_path to return Pathname(value).
def stub_pathname_expand_path(value, &block)
stub_instance_method Pathname, :expand_path, Pathname(value), &block
end
# A helper to define one or more methods (identified in +names+) on +target+
# with the same +block+, which defaults to an empty callable.
def disable_unsafe_method(target, *names, &block)
block ||= ->(*) {}
names.each { |name| target.define_singleton_method name, &block }
end
# A helper to stub Dir.chdir, which was not stubbing cleanly. This also
# asserts that the path provided to Dir.chdir will be the +expected+ value.
def stub_dir_chdir(expected)
assert_equal = method(:assert_equal)
Dir.singleton_class.send(:alias_method, :__minitest_stub_chdir__, :chdir)
Dir.singleton_class.send(:define_method, :chdir) do |path, &block|
assert_equal.call(expected, path)
block&.call(path)
end
yield
ensure
Dir.singleton_class.send(:remove_method, :chdir)
Dir.singleton_class.send(:alias_method, :chdir, :__minitest_stub_chdir__)
Dir.singleton_class.send(:remove_method, :__minitest_stub_chdir__)
end
# Stubs Cartage#run and asserts that the array of commands provided are
# matched for each call and that they are all consumed.
def stub_cartage_run(*expected)
expected = [expected].flatten(1)
stub_instance_method Cartage, :run, ->(v) { assert_equal expected.shift, v } do
yield
end
assert_empty expected
end
# :nocov:
private
# Ripped and slightly modified from minitest-stub-any-instance.
def stub_instance_method(klass, name, val_or_callable, &block)
if defined?(::Minitest::Moar::Stubbing)
instance_stub klass, name, val_or_callable, &block
elsif defined?(::Minitest::StubAnyInstance)
klass.stub_any_instance(name, val_or_callable, &block)
else
begin
new_name = "__minitest_stub_instance_method__#{name}"
owns_method = instance_method(name).owner == klass
klass.class_eval do
alias_method new_name, name if owns_method
define_method(name) do |*args|
if val_or_callable.respond_to?(:call)
instance_exec(*args, &val_or_callable)
else
val_or_callable
end
end
end
yield
ensure
klass.class_eval do
remove_method name
if owns_method
alias_method name, new_name
remove_method new_name
end
end
end
end
end
Minitest::Test.send(:include, self)
end