From 11b67e04c263d9e9011d1a583af63b270eab99e9 Mon Sep 17 00:00:00 2001 From: apainintheneck Date: Sun, 10 Mar 2024 22:00:06 -0700 Subject: [PATCH] untap: add tests for finding formulae/casks in each tap These are regression tests to make sure that this logic is reproducible. If this logic is not working, it might mean that someone removes a tap accidentally that still includes a formula or cask that they currently have installed. The tests are extravagant and over-engineered but I'm not sure that there's an easier way to do this without massive integration tests. --- .../Homebrew/test/support/helper/formula.rb | 5 +- Library/Homebrew/test/untap_spec.rb | 130 ++++++++++++++++++ 2 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 Library/Homebrew/test/untap_spec.rb diff --git a/Library/Homebrew/test/support/helper/formula.rb b/Library/Homebrew/test/support/helper/formula.rb index 02813949ba..a3c042666e 100644 --- a/Library/Homebrew/test/support/helper/formula.rb +++ b/Library/Homebrew/test/support/helper/formula.rb @@ -5,8 +5,9 @@ require "formulary" module Test module Helper module Formula - def formula(name = "formula_name", path: Formulary.core_path(name), spec: :stable, alias_path: nil, &block) - Class.new(::Formula, &block).new(name, path, spec, alias_path:) + def formula(name = "formula_name", path: Formulary.core_path(name), spec: :stable, alias_path: nil, tap: nil, + &block) + Class.new(::Formula, &block).new(name, path, spec, alias_path:, tap:) end # Use a stubbed {Formulary::FormulaLoader} to make a given formula be found diff --git a/Library/Homebrew/test/untap_spec.rb b/Library/Homebrew/test/untap_spec.rb new file mode 100644 index 0000000000..eac2c0cb28 --- /dev/null +++ b/Library/Homebrew/test/untap_spec.rb @@ -0,0 +1,130 @@ +# frozen_string_literal: true + +require "untap" + +RSpec.describe Homebrew::Untap do + describe ".installed_formulae_for" do + shared_examples "finds installed formulae in tap" do + def load_formula(name:, with_formula_file: false, mock_install: false) + formula = formula(name, tap:) do + url "https://brew.sh/foo-1.0.tgz" + end + + if with_formula_file + class_name = name.split("_").map(&:capitalize).join + tap.formula_dir.mkpath + (tap.formula_dir/"#{name}.rb").write <<~RUBY + class #{class_name} < Formula + url "https://brew.sh/foo-1.0.tgz" + end + RUBY + end + + if mock_install + keg_path = HOMEBREW_CELLAR/name/"1.2.3" + keg_path.mkpath + + tab_path = keg_path/Tab::FILENAME + tab_path.write <<~JSON + { + "source": { + "tap": "#{tap}" + } + } + JSON + end + + formula + end + + let!(:currently_installed_formula) do + load_formula(name: "current_install", with_formula_file: true, mock_install: true) + end + + before do + # Formula that is available from a tap but not installed. + load_formula(name: "no_install", with_formula_file: true) + + # Formula that was installed from a tap but is no longer available from that tap. + load_formula(name: "legacy_install", mock_install: true) + + tap.clear_cache + end + + it "returns the expected formulae" do + expect(described_class.installed_formulae_for(tap:).map(&:full_name)) + .to eq([currently_installed_formula.full_name]) + end + end + + context "with core tap" do + let(:tap) { CoreTap.instance } + + include_examples "finds installed formulae in tap" + end + + context "with non-core tap" do + let(:tap) { Tap.fetch("homebrew", "foo") } + + include_examples "finds installed formulae in tap" + end + end + + describe ".installed_casks_for" do + shared_examples "finds installed casks in tap" do + def load_cask(token:, with_cask_file: false, mock_install: false) + cask_loader = Cask::CaskLoader::FromContentLoader.new(<<~RUBY, tap:) + cask '#{token}' do + version "1.2.3" + sha256 :no_check + + url 'https://brew.sh/' + end + RUBY + + cask = cask_loader.load(config: nil) + + if with_cask_file + cask_path = tap.cask_dir/"#{token}.rb" + cask_path.parent.mkpath + cask_path.write cask.source + end + + if mock_install + metadata_subdirectory = cask.metadata_subdir("Casks", timestamp: :now, create: true) + (metadata_subdirectory/"#{token}.rb").write cask.source + end + + cask + end + + let!(:currently_installed_cask) do + load_cask(token: "current_install", with_cask_file: true, mock_install: true) + end + + before do + # Cask that is available from a tap but not installed. + load_cask(token: "no_install", with_cask_file: true) + + # Cask that was installed from a tap but is no longer available from that tap. + load_cask(token: "legacy_install", mock_install: true) + end + + it "returns the expected casks" do + expect(described_class.installed_casks_for(tap:)).to eq([currently_installed_cask]) + end + end + + context "with core cask tap" do + let(:tap) { CoreCaskTap.instance } + + include_examples "finds installed casks in tap" + end + + context "with non-core cask tap" do + let(:tap) { Tap.fetch("homebrew", "foo") } + + include_examples "finds installed casks in tap" + end + end +end